Compare commits

9 Commits

Author SHA1 Message Date
293f2bacc5 Implement Transform propagation in flecs 2025-04-20 23:52:34 +02:00
b78c214229 Introduce AssetManager 2025-04-20 19:36:17 +02:00
d643288e56 Run input and rendering in systems 2025-04-20 18:01:10 +02:00
077191dc10 Add mouse catching and escape systems to app 2025-04-19 19:12:22 +02:00
ba6a560129 Port window and input events to flecs 2025-04-19 12:56:40 +02:00
e590203234 Start ECSsing the window 2025-04-14 23:15:00 +02:00
82ddc88246 start with input 2025-04-13 21:17:35 +02:00
ecad49ee32 wip 2025-03-29 18:29:06 +01:00
942d792ba7 wip: add flecs 2025-03-26 22:45:25 +01:00
29 changed files with 440 additions and 296 deletions

View File

@@ -11,9 +11,8 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
find_package(EnTT CONFIG REQUIRED) find_package(flecs CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED) find_package(glm CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(glfw3 REQUIRED) find_package(glfw3 REQUIRED)
find_package(spdlog REQUIRED) find_package(spdlog REQUIRED)
find_package(fx-gltf REQUIRED) find_package(fx-gltf REQUIRED)
@@ -21,8 +20,8 @@ find_package(fx-gltf REQUIRED)
add_subdirectory(${PROJECT_SOURCE_DIR}/lib) add_subdirectory(${PROJECT_SOURCE_DIR}/lib)
add_library(fever_core add_library(fever_core
src/components/transform.cpp src/asset/asset.cpp
src/core/application.cpp src/transform/transform.cpp
src/core/camera.cpp src/core/camera.cpp
src/core/glad.cpp src/core/glad.cpp
src/core/graphics/framebuffer.cpp src/core/graphics/framebuffer.cpp
@@ -36,7 +35,7 @@ add_library(fever_core
src/input/input.cpp src/input/input.cpp
src/scene/gltf.cpp src/scene/gltf.cpp
src/scene/gltf_loader.cpp src/scene/gltf_loader.cpp
src/util/log.cpp src/log/log.cpp
src/window/window.cpp src/window/window.cpp
) )
@@ -48,11 +47,10 @@ target_link_libraries(
glad glad
stb stb
glfw glfw
EnTT::EnTT flecs::flecs
spdlog::spdlog spdlog::spdlog
glm::glm glm::glm
fx-gltf::fx-gltf fx-gltf::fx-gltf
nlohmann_json::nlohmann_json
) )
add_subdirectory(${PROJECT_SOURCE_DIR}/apps) add_subdirectory(${PROJECT_SOURCE_DIR}/apps)

View File

@@ -1,4 +1,4 @@
A work in progress game engine written in C++ using OpenGL and [entt](https://github.com/skypjack/entt). A work in progress game engine written in C++ using OpenGL and [flecs](https://flecs.dev).
![A screenshot](screenshot.png) ![A screenshot](screenshot.png)

View File

@@ -1 +1,2 @@
add_subdirectory(fall-fever) add_subdirectory(fall-fever)
add_subdirectory(flecs-test)

View File

@@ -2,12 +2,13 @@
#include "flycam.h" #include "flycam.h"
#include "components/name.h" #include "components/name.h"
#include "components/transform.h" #include "transform/transform.h"
#include "core/camera.h" #include "core/camera.h"
#include "core/light.h" #include "core/light.h"
#include "window/window.h" #include "window/window.h"
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <flecs.h>
using namespace entt::literals; using namespace entt::literals;
@@ -57,7 +58,7 @@ void Controller::update()
{ {
Flycam::keyboard_movement(registry()); Flycam::keyboard_movement(registry());
if (registry().ctx().get<Window::MouseCatched>().catched) { if (registry().ctx().get<WindowDeprecated::MouseCatched>().catched) {
Flycam::mouse_orientation(registry()); Flycam::mouse_orientation(registry());
} }
} }

View File

@@ -17,7 +17,7 @@ void Flycam::keyboard_movement(entt::registry& registry)
}; };
auto& movement_context = registry.ctx().emplace<KeyboardMovementContext>(); auto& movement_context = registry.ctx().emplace<KeyboardMovementContext>();
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>(); auto const& key_state = registry.ctx().get<Input::ButtonInput<Input::KeyCode>>();
auto const& delta_time = registry.ctx().get<Time::Delta>(); auto const& delta_time = registry.ctx().get<Time::Delta>();
auto camera_view = auto camera_view =

View File

@@ -0,0 +1,5 @@
add_executable(flecs-test
main.cpp
)
target_link_libraries(flecs-test PRIVATE fever_core)

67
apps/flecs-test/main.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include <asset/asset.h>
#include <input/input.h>
#include <log/log.h>
#include <transform/transform.h>
#include <window/window.h>
#include <GLFW/glfw3.h>
#include <flecs.h>
#include <iostream>
int main()
{
Log::initialize();
flecs::world world;
world.import <Window::WindowModule>();
world.import <Input::InputModule>();
world.import <Asset::AssetModule>();
world.import <TransformModule>();
#ifndef NDEBUG
world.import <flecs::stats>();
world.set<flecs::Rest>({});
#endif
world.system<Window::Window, Input::ButtonInput<Input::KeyCode> const>("CatchMouse")
.each([](flecs::entity e,
Window::Window,
Input::ButtonInput<Input::KeyCode> const& button_input) {
if (button_input.just_pressed(Input::KeyCode(GLFW_KEY_LEFT_CONTROL))) {
auto glfw_window = e.get<std::shared_ptr<GLFWwindow>>();
auto input_mode = glfwGetInputMode(glfw_window->get(), GLFW_CURSOR);
if (input_mode == GLFW_CURSOR_NORMAL) {
glfwSetInputMode(glfw_window->get(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
} else {
glfwSetInputMode(glfw_window->get(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
}
});
world.system<Window::Window, Input::ButtonInput<Input::KeyCode> const>("CloseOnEsc")
.each([](flecs::entity e,
Window::Window,
Input::ButtonInput<Input::KeyCode> const& button_input) {
if (button_input.just_pressed(Input::KeyCode(GLFW_KEY_ESCAPE))) {
e.world().quit();
}
});
world.system("PollEvents").kind(flecs::PreUpdate).run([](flecs::iter&) { glfwPollEvents(); });
world.system<std::shared_ptr<GLFWwindow>>("Render")
.kind(flecs::PostUpdate)
.each([](std::shared_ptr<GLFWwindow>& glfw_window) { glfwSwapBuffers(glfw_window.get()); });
auto parent = world.entity("TestParent").add<Transform>().add<GlobalTransform>();
parent.get_mut<Transform>()->translation += glm::vec3(1.0);
auto child = world.entity("TestChild").add<Transform>().add<GlobalTransform>();
child.get_mut<Transform>()->translation += glm::vec3(1.0);
child.child_of(parent);
while (world.progress()) {
}
}

9
src/asset/asset.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "asset.h"
namespace Asset {
AssetModule::AssetModule([[maybe_unused]] flecs::world& world)
{
}
} // namespace Asset

42
src/asset/asset.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <flecs.h>
#include <memory>
#include <string>
#include <unordered_map>
namespace Asset {
struct AssetModule
{
AssetModule(flecs::world& world);
};
template <typename T> struct AssetCache
{
std::unordered_map<std::string, std::shared_ptr<T>> cache;
};
template <typename T> struct AssetLoader
{
template <typename Args> std::shared_ptr<T> operator()(Args&&...)
{
static_assert(sizeof(T) == 0, "AssetLoader not implemented for this type.");
}
};
template <typename T> std::shared_ptr<T> load(flecs::world& world, std::string asset_path)
{
auto& asset_cache = world.ensure<AssetCache<T>>();
auto asset_it = asset_cache.cache.find(asset_path);
if (asset_it != asset_cache.cache.end())
return asset_it->second;
AssetLoader<T> asset_loader;
auto loaded_asset = asset_loader(asset_path);
asset_cache.cache.emplace(asset_path, loaded_asset);
return loaded_asset;
}
} // namespace Asset

View File

@@ -1,8 +0,0 @@
#pragma once
#include <string>
struct Name
{
std::string name;
};

View File

@@ -1,37 +0,0 @@
#include "transform.h"
#include "relationship.h"
void GlobalTransform::update(entt::registry &registry)
{
// Update GlobalTransform components
// TODO: Only do this when the Transform changed.
auto root_transform_view =
registry.view<Transform const, GlobalTransform>(entt::exclude<Parent>);
auto transform_view = registry.view<Transform const, GlobalTransform, Parent const>();
for (auto [entity, transform, global_transform] : root_transform_view.each()) {
global_transform = transform;
auto parent_global_transform = global_transform;
if (auto *children = registry.try_get<Children>(entity)) {
for (auto child : children->children) {
std::function<void(entt::entity entity, GlobalTransform parent_global_transform)>
transform_propagate =
[&registry, &transform_propagate, &transform_view](
entt::entity entity, GlobalTransform parent_global_transform) {
auto [transform, global_transform, parent] = transform_view.get(entity);
global_transform.transform = parent_global_transform.transform *
GlobalTransform(transform).transform;
if (auto *children = registry.try_get<Children>(entity)) {
for (auto child : children->children) {
transform_propagate(child, global_transform);
}
}
};
transform_propagate(child, parent_global_transform);
}
}
}
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include <entt/entt.hpp>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
struct Transform
{
glm::vec3 translation{};
glm::quat orientation{};
glm::vec3 scale{1.0, 1.0, 1.0};
};
struct GlobalTransform
{
GlobalTransform() = default;
GlobalTransform(Transform const &transform)
{
// Translate * Rotate * Scale * vertex_vec;
// First scaling, then rotation, then translation
// Translate
glm::mat4 translation_matrix = glm::translate(glm::mat4(1.0F), transform.translation);
// Rotate
glm::mat4 rotation_matrix = glm::toMat4(transform.orientation);
// Scale
glm::mat4 scale_matrix = glm::scale(glm::mat4(1.0F), transform.scale);
this->transform = translation_matrix * rotation_matrix * scale_matrix;
}
glm::mat4 transform{};
[[nodiscard]] auto position() const -> glm::vec3 { return transform[3]; };
static void update(entt::registry &registry);
};

View File

@@ -20,7 +20,7 @@ namespace FeverCore {
Application::~Application() = default; Application::~Application() = default;
Application::Application() : Application::Application() :
game_window(std::make_shared<Window>(event_dispatcher)), game_window(std::make_shared<Window>(&_world)),
post_processing_framebuffer(game_window->physical_dimensions()), post_processing_framebuffer(game_window->physical_dimensions()),
key_listener{.registry = entt_registry}, key_listener{.registry = entt_registry},
cursor_listener{.registry = entt_registry}, cursor_listener{.registry = entt_registry},
@@ -68,7 +68,7 @@ void Application::run()
this->update(); this->update();
Input::State<Input::KeyCode>::update_state(entt_registry); // Input::State<Input::KeyCode>::update_state(entt_registry);
Input::reset_mouse_motion(entt_registry); Input::reset_mouse_motion(entt_registry);
// --- Render and buffer swap --- // --- Render and buffer swap ---

View File

@@ -2,17 +2,16 @@
#include "core/graphics/framebuffer.h" #include "core/graphics/framebuffer.h"
#include "core/shader.h" #include "core/shader.h"
#include "entt/entity/fwd.hpp"
#include "entt/signal/fwd.hpp"
#include "input/input.h" #include "input/input.h"
#include "scene/gltf_loader.h" #include "scene/gltf_loader.h"
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <flecs.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory> #include <memory>
class Camera; class Camera;
class Window; class WindowDeprecated;
namespace FeverCore { namespace FeverCore {
@@ -30,8 +29,8 @@ public:
auto registry() -> entt::registry& { return entt_registry; } auto registry() -> entt::registry& { return entt_registry; }
auto registry() const -> entt::registry const& { return entt_registry; } auto registry() const -> entt::registry const& { return entt_registry; }
auto dispatcher() -> entt::dispatcher& { return event_dispatcher; } auto world() -> flecs::world& { return _world; }
auto dispatcher() const -> entt::dispatcher const& { return event_dispatcher; } auto world() const -> flecs::world const& { return _world; }
void run(); void run();
@@ -41,12 +40,13 @@ protected:
virtual void register_context_variables(); virtual void register_context_variables();
void recreate_framebuffer(); void recreate_framebuffer();
std::shared_ptr<Window> game_window; std::shared_ptr<WindowDeprecated> game_window;
Shader post_processing_shader{"post_processing", "data/shaders"}; Shader post_processing_shader{"post_processing", "data/shaders"};
Framebuffer post_processing_framebuffer; Framebuffer post_processing_framebuffer;
entt::registry entt_registry; entt::registry entt_registry;
flecs::world _world;
entt::dispatcher event_dispatcher{}; entt::dispatcher event_dispatcher{};
Input::KeyListener key_listener; Input::KeyListener key_listener;

View File

@@ -32,13 +32,13 @@ auto Camera::front_vector(GlobalTransform const& transform) -> glm::vec3
void Camera::aspect_ratio_update(entt::registry& registry) void Camera::aspect_ratio_update(entt::registry& registry)
{ {
float aspect_ratio = registry.ctx().get<Window::Descriptor>().aspect_ratio; // float aspect_ratio = registry.ctx().get<WindowDeprecated::Descriptor>().aspect_ratio;
auto camera_view = registry.view<Camera>(); // auto camera_view = registry.view<Camera>();
for (auto [entity, camera] : camera_view.each()) { // for (auto [entity, camera] : camera_view.each()) {
// Orthographic projection currently unsupported // // Orthographic projection currently unsupported
auto& camera_perspective = std::get<Perspective>(camera.projection); // auto& camera_perspective = std::get<Perspective>(camera.projection);
camera_perspective.aspect_ratio = aspect_ratio; // camera_perspective.aspect_ratio = aspect_ratio;
} // }
} }

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "components/transform.h" #include "transform/transform.h"
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <glm/glm.hpp> #include <glm/glm.hpp>

View File

@@ -1,5 +1,5 @@
#include "light.h" #include "light.h"
#include "components/transform.h" #include "transform/transform.h"
static auto light_active(float illuminance) -> bool static auto light_active(float illuminance) -> bool
{ {

View File

@@ -4,36 +4,42 @@
namespace Input { namespace Input {
void KeyListener::key_event(KeyInput const& key_input_event) void keyboard_input_observer(KeyboardInput const& keyboard_input);
{ template <typename T> void clear_button_input(ButtonInput<T>& state);
auto& key_state = registry.ctx().get<State<KeyCode>>();
if (key_input_event.action == static_cast<Action>(GLFW_PRESS)) { InputModule::InputModule(flecs::world& world)
key_state.press(key_input_event.key_code); {
} else if (key_input_event.action == static_cast<Action>(GLFW_RELEASE)) { world.component<KeyboardInput>();
key_state.release(key_input_event.key_code); world.component<MouseButtonInput>();
world.component<MouseMotion>();
world.component<ButtonInput<KeyCode>>();
world.system<ButtonInput<KeyCode>>("ClearButtonInput")
.kind(flecs::PostUpdate)
.each(clear_button_input<KeyCode>);
world.observer<KeyboardInput>("KeyboardInputObserver")
.event(flecs::OnSet)
.each(keyboard_input_observer);
}
void keyboard_input_observer(KeyboardInput const& keyboard_input)
{
auto* key_state = keyboard_input.window.get_mut<ButtonInput<KeyCode>>();
if (keyboard_input.action == static_cast<Input::Action>(GLFW_PRESS)) {
key_state->press(keyboard_input.key_code);
} else if (keyboard_input.action == static_cast<Input::Action>(GLFW_RELEASE)) {
key_state->release(keyboard_input.key_code);
} }
} }
void CursorListener::cursor_event(MouseMotion const& mouse_motion_event) template <typename T> void clear_button_input(ButtonInput<T>& state)
{ {
auto& mouse_motion = registry.ctx().get<MouseMotion>();
mouse_motion.delta += mouse_motion_event.delta;
}
void reset_mouse_motion(entt::registry& registry)
{
auto& mouse_motion = registry.ctx().get<MouseMotion>();
mouse_motion = {};
}
template <typename T> void State<T>::update_state(entt::registry& registry)
{
auto& state = registry.ctx().get<State<T>>();
state.just_pressed_keys.clear(); state.just_pressed_keys.clear();
state.just_released_keys.clear(); state.just_released_keys.clear();
} }
template class State<KeyCode>; template class ButtonInput<KeyCode>;
} // namespace Input } // namespace Input

View File

@@ -1,13 +1,16 @@
#pragma once #pragma once
#include "entt/entity/fwd.hpp" #include <flecs.h>
#include <entt/entt.hpp>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <set> #include <set>
#include <utility>
namespace Input { namespace Input {
struct InputModule
{
InputModule(flecs::world& world);
};
enum class KeyCode : int enum class KeyCode : int
{ {
}; };
@@ -16,10 +19,18 @@ enum class Action : int
{ {
}; };
struct KeyInput struct KeyboardInput
{ {
KeyCode key_code; KeyCode key_code;
Action action; Action action;
flecs::entity window;
};
struct MouseButtonInput
{
KeyCode key_code;
Action action;
flecs::entity window;
}; };
struct MouseMotion struct MouseMotion
@@ -27,16 +38,14 @@ struct MouseMotion
glm::vec2 delta{}; glm::vec2 delta{};
}; };
template <typename T> class State template <typename T> class ButtonInput
{ {
public: public:
auto pressed(T input) const -> bool { return pressed_keys.contains(input); } auto pressed(T input) const -> bool { return pressed_keys.contains(input); }
auto just_pressed(T input) const -> bool { return just_pressed_keys.contains(input); } auto just_pressed(T input) const -> bool { return just_pressed_keys.contains(input); }
auto just_released(T input) const -> bool { return just_pressed_keys.contains(input); } auto just_released(T input) const -> bool { return just_pressed_keys.contains(input); }
static void update_state(entt::registry& registry); // private:
private:
void press(T input) void press(T input)
{ {
if (pressed_keys.insert(input).second) { if (pressed_keys.insert(input).second) {
@@ -54,23 +63,6 @@ private:
std::set<T> pressed_keys; std::set<T> pressed_keys;
std::set<T> just_pressed_keys; std::set<T> just_pressed_keys;
std::set<T> just_released_keys; std::set<T> just_released_keys;
friend class KeyListener;
friend class CursorListener;
}; };
struct KeyListener
{
entt::registry& registry;
void key_event(KeyInput const& key_input_event);
};
struct CursorListener
{
entt::registry& registry;
void cursor_event(MouseMotion const& mouse_motion_event);
};
void reset_mouse_motion(entt::registry& registry);
}; // namespace Input }; // namespace Input

View File

@@ -1,5 +1,4 @@
#include "gltf.h" #include "gltf.h"
#include "components/name.h"
#include "components/relationship.h" #include "components/relationship.h"
#include "core/camera.h" #include "core/camera.h"
@@ -38,7 +37,7 @@ auto Gltf::spawn_scene(std::size_t index,
std::function<entt::entity(GltfNode const&, entt::entity)> spawn_node = std::function<entt::entity(GltfNode const&, entt::entity)> spawn_node =
[&registry, &spawn_node](GltfNode const& node, entt::entity parent) { [&registry, &spawn_node](GltfNode const& node, entt::entity parent) {
auto entity = registry.create(); auto entity = registry.create();
registry.emplace<Name>(entity, node.name); // registry.emplace<Name>(entity, node.name);
registry.emplace<Transform>(entity, node.transform); registry.emplace<Transform>(entity, node.transform);
registry.emplace<GlobalTransform>(entity, GlobalTransform{}); registry.emplace<GlobalTransform>(entity, GlobalTransform{});
registry.emplace<Parent>(entity, Parent{.parent = parent}); registry.emplace<Parent>(entity, Parent{.parent = parent});

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "components/transform.h" #include "transform/transform.h"
#include "core/graphics/material.h" #include "core/graphics/material.h"
#include "core/graphics/mesh.h" #include "core/graphics/mesh.h"

View File

@@ -1,5 +1,4 @@
#include "gltf_loader.h" #include "gltf_loader.h"
#include "components/name.h"
#include "components/relationship.h" #include "components/relationship.h"
#include "core/camera.h" #include "core/camera.h"
#include "entt/entity/fwd.hpp" #include "entt/entity/fwd.hpp"

View File

@@ -0,0 +1,36 @@
#include "transform.h"
void GlobalTransform::update(entt::registry& registry)
{
// Update GlobalTransform components
// TODO: Only do this when the Transform changed.
// auto root_transform_view =
// registry.view<Transform const, GlobalTransform>(entt::exclude<Parent>);
// auto transform_view = registry.view<Transform const, GlobalTransform, Parent const>();
// for (auto [entity, transform, global_transform] : root_transform_view.each()) {
// global_transform = transform;
// auto parent_global_transform = global_transform;
// if (auto* children = registry.try_get<Children>(entity)) {
// for (auto child : children->children) {
// std::function<void(entt::entity entity, GlobalTransform parent_global_transform)>
// transform_propagate =
// [&registry, &transform_propagate, &transform_view](
// entt::entity entity, GlobalTransform parent_global_transform) {
// auto [transform, global_transform, parent] = transform_view.get(entity);
// global_transform.transform = parent_global_transform.transform *
// GlobalTransform(transform).transform;
// if (auto* children = registry.try_get<Children>(entity)) {
// for (auto child : children->children) {
// transform_propagate(child, global_transform);
// }
// }
// };
// transform_propagate(child, parent_global_transform);
// }
// }
// }
}

68
src/transform/transform.h Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <entt/entt.hpp>
#include <flecs.h>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
struct Transform
{
glm::vec3 translation{};
glm::quat orientation{};
glm::vec3 scale{1.0, 1.0, 1.0};
};
struct GlobalTransform
{
GlobalTransform() = default;
GlobalTransform(Transform const& transform)
{
// Translate * Rotate * Scale * vertex_vec;
// First scaling, then rotation, then translation
// Translate
glm::mat4 translation_matrix = glm::translate(glm::mat4(1.0F), transform.translation);
// Rotate
glm::mat4 rotation_matrix = glm::toMat4(transform.orientation);
// Scale
glm::mat4 scale_matrix = glm::scale(glm::mat4(1.0F), transform.scale);
this->transform = translation_matrix * rotation_matrix * scale_matrix;
}
glm::mat4 transform{};
[[nodiscard]] auto position() const -> glm::vec3 { return transform[3]; };
static void update(entt::registry& registry);
};
// namespace Transform {
struct TransformModule
{
TransformModule(flecs::world& world)
{
world.system<GlobalTransform, Transform const>("PropagateTransform")
.kind(flecs::PostUpdate)
.each([](flecs::entity e,
GlobalTransform& global_transform,
Transform const& local_transform) {
// There is no guarantee that the parent is iterated first. So there could be a two
// frame delay.
if (e.parent() == flecs::entity::null()) {
global_transform.transform = GlobalTransform(local_transform).transform;
} else {
auto parent_global_transform = e.parent().get_mut<GlobalTransform>();
global_transform.transform = parent_global_transform->transform *
GlobalTransform(local_transform).transform;
}
});
}
};
// } // namespace Transform

View File

@@ -7,10 +7,33 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
namespace Window {
static constexpr unsigned INIT_WINDOW_WIDTH = 1280; static constexpr unsigned INIT_WINDOW_WIDTH = 1280;
static constexpr unsigned INIT_WINDOW_HEIGHT = 720; static constexpr unsigned INIT_WINDOW_HEIGHT = 720;
static constexpr double MOUSE_SENSITIVITY = 0.15;
Window::Window(entt::dispatcher& event_dispatcher) : event_dispatcher(event_dispatcher) static void glfw_error_callback(int error, char const* description);
static void framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height);
static void window_size_callback(GLFWwindow* glfw_window, int width, int height);
static void window_close_callback(GLFWwindow* glfw_window);
static void key_callback(GLFWwindow* glfw_window, int key, int scancode, int action, int mods);
static void mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos);
WindowModule::WindowModule(flecs::world& world)
{
glfwInit();
world.component<Window>();
world.component<PrimaryWindow>();
world.component<std::shared_ptr<GLFWwindow>>();
world.component<std::optional<CursorPosition>>();
world.component<LogicalSize>();
world.component<PhysicalSize>();
spawn_window(world);
}
flecs::entity spawn_window(flecs::world& world)
{ {
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
@@ -24,7 +47,7 @@ Window::Window(entt::dispatcher& event_dispatcher) : event_dispatcher(event_disp
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
#endif #endif
glfw_window = std::shared_ptr<GLFWwindow>( auto glfw_window = std::shared_ptr<GLFWwindow>(
glfwCreateWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "OpenGL", nullptr, nullptr), glfwCreateWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "OpenGL", nullptr, nullptr),
[](GLFWwindow* window) { glfwDestroyWindow(window); }); [](GLFWwindow* window) { glfwDestroyWindow(window); });
@@ -36,110 +59,100 @@ Window::Window(entt::dispatcher& event_dispatcher) : event_dispatcher(event_disp
glfwMakeContextCurrent(glfw_window.get()); glfwMakeContextCurrent(glfw_window.get());
// Callbacks // Callbacks
glfwSetWindowUserPointer(glfw_window.get(), this); glfwSetWindowUserPointer(glfw_window.get(), &world);
glfwSetKeyCallback(glfw_window.get(), key_callback); glfwSetKeyCallback(glfw_window.get(), key_callback);
glfwSetCursorPosCallback(glfw_window.get(), mouse_cursor_callback); glfwSetCursorPosCallback(glfw_window.get(), mouse_cursor_callback);
glfwSetWindowSizeCallback(glfw_window.get(), window_size_callback);
glfwSetWindowCloseCallback(glfw_window.get(), window_close_callback);
glfwSetFramebufferSizeCallback(glfw_window.get(), framebuffer_size_callback); glfwSetFramebufferSizeCallback(glfw_window.get(), framebuffer_size_callback);
init_glad(); init_glad();
{ int window_width, window_height;
int width, height; glfwGetWindowSize(glfw_window.get(), &window_width, &window_height);
glfwGetFramebufferSize(glfw_window.get(), &width, &height);
glViewport(0, 0, width, height); int framebuffer_width, framebuffer_height;
} glfwGetFramebufferSize(glfw_window.get(), &framebuffer_width, &framebuffer_height);
glViewport(0, 0, framebuffer_width, framebuffer_height);
flecs::entity window = world.entity("PrimaryWindow");
window.add<Window>();
window.add<Input::ButtonInput<Input::KeyCode>>();
window.add<std::optional<CursorPosition>>();
window.set<std::shared_ptr<GLFWwindow>>(glfw_window);
window.set<LogicalSize>({window_width, window_height});
window.set<PhysicalSize>({framebuffer_width, framebuffer_height});
world.set<PrimaryWindow>({window});
return window;
} }
void Window::glfw_error_callback(int error, char const* description) static void glfw_error_callback(int error, char const* description)
{ {
spdlog::warn("GLFW [{:d}]: {:s}\n", error, description); spdlog::warn("GLFW [{:d}]: {:s}\n", error, description);
} }
void Window::framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height) void window_size_callback(GLFWwindow* glfw_window, int width, int height)
{ {
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window)); auto* world = static_cast<flecs::world*>(glfwGetWindowUserPointer(glfw_window));
glViewport(0, 0, width, height); auto window = world->get<PrimaryWindow>()->window;
window.event_dispatcher.enqueue<ResizeEvent>(); window.set<LogicalSize>({width, height});
} }
void Window::key_callback(GLFWwindow* glfw_window, void window_close_callback(GLFWwindow* glfw_window)
{
auto* world = static_cast<flecs::world*>(glfwGetWindowUserPointer(glfw_window));
world->quit();
}
void framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height)
{
auto* world = static_cast<flecs::world*>(glfwGetWindowUserPointer(glfw_window));
auto window = world->get<PrimaryWindow>()->window;
window.set<PhysicalSize>({width, height});
glViewport(0, 0, width, height);
}
void key_callback(GLFWwindow* glfw_window,
int key, int key,
[[maybe_unused]] int scancode, [[maybe_unused]] int scancode,
int action, int action,
[[maybe_unused]] int mods) [[maybe_unused]] int mods)
{ {
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window)); auto* world = static_cast<flecs::world*>(glfwGetWindowUserPointer(glfw_window));
window.event_dispatcher.enqueue<Input::KeyInput>( // Maybe lookup with the glfw pointer instead?
Input::KeyInput{.key_code = static_cast<Input::KeyCode>(key), auto window = world->get<PrimaryWindow>()->window;
.action = static_cast<Input::Action>(action)});
world->component<Input::KeyboardInput>().set<Input::KeyboardInput>(
{.key_code = static_cast<Input::KeyCode>(key),
.action = static_cast<Input::Action>(action),
.window = window});
} }
void Window::mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos) void mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos)
{ {
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window)); auto* world = static_cast<flecs::world*>(glfwGetWindowUserPointer(glfw_window));
auto window = world->get<PrimaryWindow>()->window;
glm::vec2 delta{xpos - window.last_cursor_pos.x, ypos - window.last_cursor_pos.y}; auto cursor_position = window.get_mut<std::optional<CursorPosition>>();
bool had_value = cursor_position->has_value();
*cursor_position = {xpos, ypos};
// Prevent invalid delta events when the cursor position wasn't valid
if (!had_value)
return;
glm::vec2 delta{xpos - cursor_position->value().x, ypos - cursor_position->value().y};
window.last_cursor_pos = {xpos, ypos};
delta *= MOUSE_SENSITIVITY; delta *= MOUSE_SENSITIVITY;
// Check if this is the first VALID mouse event after window being resized world->component<Input::MouseMotion>().set<Input::MouseMotion>({delta});
if (window.first_mouse_input && (delta.x >= std::numeric_limits<double>::epsilon() ||
delta.y >= std::numeric_limits<double>::epsilon())) {
window.first_mouse_input = false;
return;
} }
window.event_dispatcher.enqueue<Input::MouseMotion>(Input::MouseMotion{.delta = delta}); } // namespace Window
}
auto Window::logical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetWindowSize(glfw_window.get(), &width, &height);
return {width, height};
}
auto Window::physical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetFramebufferSize(glfw_window.get(), &width, &height);
return {width, height};
}
void Window::mouse_catching(entt::registry& registry) const
{
auto& mouse_catched = registry.ctx().emplace<MouseCatched>(MouseCatched{.catched = false});
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>();
if (key_state.just_pressed(Input::KeyCode{GLFW_KEY_LEFT_CONTROL})) {
mouse_catched.catched = !mouse_catched.catched;
glfwSetInputMode(glfw_window.get(),
GLFW_CURSOR,
mouse_catched.catched ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
}
}
void Window::close_on_esc(entt::registry& registry) const
{
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>();
if (key_state.just_pressed(Input::KeyCode{GLFW_KEY_ESCAPE})) {
glfwSetWindowShouldClose(glfw_window.get(), GLFW_TRUE);
}
}
void Window::update_descriptor(entt::registry& registry) const
{
auto dimensions = logical_dimensions();
registry.ctx().erase<Descriptor>();
registry.ctx().emplace<Descriptor>(Descriptor{
.logical_dimensions = dimensions,
.aspect_ratio = static_cast<float>(dimensions.x) / static_cast<float>(dimensions.y)});
}

View File

@@ -1,52 +1,45 @@
#pragma once #pragma once
#include <entt/entt.hpp> #include <input/input.h>
#include <flecs.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <utility>
class GLFWwindow; class GLFWwindow;
class Window namespace Window {
struct WindowModule
{ {
public: WindowModule(flecs::world& world);
struct MouseCatched
{
bool catched = true;
}; };
struct Descriptor struct Window
{
glm::u32vec2 logical_dimensions;
float aspect_ratio{};
};
struct ResizeEvent
{}; {};
Window(entt::dispatcher& event_dispatcher); struct PrimaryWindow
{
[[nodiscard]] auto handle() -> GLFWwindow& { return *glfw_window; } flecs::entity window;
[[nodiscard]] auto physical_dimensions() const -> glm::u32vec2;
[[nodiscard]] auto logical_dimensions() const -> glm::u32vec2;
void update_descriptor(entt::registry& registry) const;
void mouse_catching(entt::registry& registry) const;
void close_on_esc(entt::registry& registry) const;
private:
static void key_callback(GLFWwindow* glfw_window, int key, int scancode, int action, int mods);
static void mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos);
static void framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height);
static void glfw_error_callback(int error, char const* description);
static constexpr float MOUSE_SENSITIVITY = 0.15F;
std::shared_ptr<GLFWwindow> glfw_window;
entt::dispatcher& event_dispatcher;
glm::vec2 last_cursor_pos{};
bool first_mouse_input = true;
}; };
struct CursorPosition
{
double x;
double y;
};
struct LogicalSize
{
int width;
int height;
};
struct PhysicalSize
{
int width;
int height;
};
flecs::entity spawn_window(flecs::world& world);
} // namespace Window

View File

@@ -8,6 +8,7 @@
"cxxopts", "cxxopts",
"entt", "entt",
"fmt", "fmt",
"fx-gltf",
{ {
"name": "glfw3", "name": "glfw3",
"features": [ "features": [
@@ -15,9 +16,9 @@
] ]
}, },
"glm", "glm",
"nlohmann-json",
"spdlog", "spdlog",
"fx-gltf" "fx-gltf",
"flecs"
], ],
"builtin-baseline": "c14d62387153eaa2d720113542dbde2e9754ee71" "builtin-baseline": "ce613c41372b23b1f51333815feb3edd87ef8a8b"
} }