Introduce new scene loading system
This commit is contained in:
@@ -8,6 +8,10 @@ project(
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
set(CMAKE_ARCHIVE_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_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
include(FetchContent)
|
||||
@@ -15,7 +19,7 @@ include(FetchContent)
|
||||
# EnTT
|
||||
FetchContent_Declare(
|
||||
entt
|
||||
URL https://github.com/skypjack/entt/archive/refs/tags/v3.11.1.tar.gz
|
||||
URL https://github.com/skypjack/entt/archive/refs/tags/v3.12.0.tar.gz
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(entt)
|
||||
@@ -27,6 +31,8 @@ FetchContent_Declare(
|
||||
)
|
||||
|
||||
option(GLFW_BUILD_DOCS "" OFF)
|
||||
option(GLFW_BUILD_EXAMPLES "" OFF)
|
||||
option(GLFW_BUILD_TESTS "" OFF)
|
||||
option(GLFW_INSTALL "" OFF)
|
||||
FetchContent_MakeAvailable(glfw)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"generator": "Unix Makefiles",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"inherits": [
|
||||
"std"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
build/src/Fall-Fever
|
||||
@@ -5,14 +5,15 @@ add_library(fever_engine
|
||||
core/graphics/material.cpp
|
||||
core/graphics/mesh.cpp
|
||||
core/camera.cpp
|
||||
core/game_loop.cpp
|
||||
core/glad.cpp
|
||||
core/light.cpp
|
||||
core/render.cpp
|
||||
core/shader.cpp
|
||||
core/time.cpp
|
||||
input/input.cpp
|
||||
scene/gltf.cpp
|
||||
scene/gltf_loader.cpp
|
||||
scene/scene.cpp
|
||||
util/log.cpp
|
||||
window/window.cpp
|
||||
)
|
||||
@@ -35,7 +36,7 @@ target_link_libraries(
|
||||
|
||||
add_executable(Fall-Fever
|
||||
bin/main.cpp
|
||||
bin/Controller.cpp
|
||||
bin/controller.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(Fall-Fever PRIVATE fever_engine)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#include "Controller.h"
|
||||
#include "core/light.h"
|
||||
#include "core/render.h"
|
||||
#include "core/shader.h"
|
||||
#include "core/time.h"
|
||||
#include "input/input.h"
|
||||
#include "scene/scene.h"
|
||||
#include "window/window.h"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fx/gltf.h>
|
||||
#include <glad/gl.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
using namespace entt::literals;
|
||||
|
||||
Controller::Controller() :
|
||||
m_gameWindow(std::make_shared<Window>(event_dispatcher)),
|
||||
post_processing_framebuffer(m_gameWindow->physical_dimensions()),
|
||||
m_gltf_loader{.image_cache = m_image_cache,
|
||||
.material_cache = m_material_cache,
|
||||
.mesh_cache = m_mesh_cache,
|
||||
.shader_cache = m_shader_cache,
|
||||
.scene_cache = m_scene_cache,
|
||||
.gltf_mesh_cache = m_gltf_mesh_cache,
|
||||
.gltf_node_cache = m_gltf_node_cache,
|
||||
.registry = registry},
|
||||
m_gltf_cache(m_gltf_loader),
|
||||
key_listener{.registry = registry},
|
||||
cursor_listener{.registry = registry}
|
||||
{
|
||||
std::filesystem::path document_path("WaterBottle/glTF-Binary/WaterBottle.glb");
|
||||
entt::hashed_string document_hash(document_path.c_str());
|
||||
|
||||
entt::resource<Gltf> gltf_document =
|
||||
m_gltf_cache.load(document_hash, document_path).first->second;
|
||||
|
||||
m_scene = gltf_document->default_scene.value_or(gltf_document->scenes.at(0)).handle();
|
||||
|
||||
Input::State<Input::KeyCode>::init_state(registry);
|
||||
|
||||
event_dispatcher.sink<Window::ResizeEvent>().connect<&Controller::recreate_framebuffer>(this);
|
||||
event_dispatcher.sink<Input::KeyInput>().connect<&Input::KeyListener::key_event>(key_listener);
|
||||
event_dispatcher.sink<Input::MouseMotion>().connect<&Input::CursorListener::cursor_event>(cursor_listener);
|
||||
}
|
||||
|
||||
void Controller::run()
|
||||
{
|
||||
entt::hashed_string shader_hash(Material::SHADER_NAME.data());
|
||||
auto standard_material_shader =
|
||||
m_shader_cache.load(shader_hash, Material::SHADER_NAME).first->second;
|
||||
|
||||
spdlog::info("Startup complete. Enter game loop.");
|
||||
|
||||
// This is the game loop
|
||||
while (glfwWindowShouldClose(&m_gameWindow->handle()) == GLFW_FALSE) {
|
||||
// --- Timing ---
|
||||
Time::update_delta_time(registry);
|
||||
|
||||
// --- Check events, handle input ---
|
||||
// m_gameWindow->clear_mouse_cursor_input();
|
||||
glfwPollEvents();
|
||||
|
||||
// --- Update game state ---
|
||||
event_dispatcher.update();
|
||||
|
||||
m_gameWindow->update_descriptor(registry);
|
||||
m_gameWindow->mouse_catching(registry);
|
||||
m_gameWindow->close_on_esc(registry);
|
||||
|
||||
m_scene->update();
|
||||
|
||||
Input::State<Input::KeyCode>::update_state(registry);
|
||||
Input::reset_mouse_motion(registry);
|
||||
|
||||
// --- Render and buffer swap ---
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
post_processing_framebuffer.bind();
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
Light::update_lights(registry, standard_material_shader);
|
||||
Render::render(registry);
|
||||
|
||||
Framebuffer::unbind();
|
||||
post_processing_framebuffer.draw(post_processing_shader);
|
||||
|
||||
glfwSwapBuffers(&m_gameWindow->handle());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::recreate_framebuffer()
|
||||
{
|
||||
auto dimensions = m_gameWindow->physical_dimensions();
|
||||
post_processing_framebuffer = Framebuffer(dimensions);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/graphics/framebuffer.h"
|
||||
#include "core/shader.h"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "scene/gltf_loader.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Scene;
|
||||
class Camera;
|
||||
class Window;
|
||||
class Framebuffer;
|
||||
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
Controller();
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
void recreate_framebuffer();
|
||||
|
||||
std::shared_ptr<Window> m_gameWindow;
|
||||
std::shared_ptr<Scene> m_scene;
|
||||
|
||||
Shader post_processing_shader{"post_processing", "data/shaders"};
|
||||
Framebuffer post_processing_framebuffer;
|
||||
|
||||
entt::registry registry;
|
||||
|
||||
entt::dispatcher event_dispatcher{};
|
||||
Input::KeyListener key_listener;
|
||||
Input::CursorListener cursor_listener;
|
||||
|
||||
// Resource caches
|
||||
entt::resource_cache<Image> m_image_cache;
|
||||
entt::resource_cache<Material> m_material_cache;
|
||||
entt::resource_cache<Mesh> m_mesh_cache;
|
||||
entt::resource_cache<Scene> m_scene_cache;
|
||||
entt::resource_cache<Shader, ShaderLoader> m_shader_cache;
|
||||
entt::resource_cache<GltfMesh> m_gltf_mesh_cache;
|
||||
entt::resource_cache<GltfNode> m_gltf_node_cache;
|
||||
|
||||
GltfLoader m_gltf_loader;
|
||||
entt::resource_cache<Gltf, GltfLoader> m_gltf_cache;
|
||||
};
|
||||
65
src/bin/controller.cpp
Normal file
65
src/bin/controller.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "controller.h"
|
||||
#include "components/name.h"
|
||||
#include "components/transform.h"
|
||||
#include "core/camera.h"
|
||||
#include "core/light.h"
|
||||
#include "window/window.h"
|
||||
|
||||
using namespace entt::literals;
|
||||
|
||||
Controller::Controller()
|
||||
{
|
||||
std::filesystem::path document_path("WaterBottle/glTF-Binary/WaterBottle.glb");
|
||||
entt::hashed_string document_hash(document_path.c_str());
|
||||
|
||||
entt::resource<Gltf> gltf_document =
|
||||
gltf_cache.load(document_hash, document_path).first->second;
|
||||
|
||||
gltf_document->spawn_default_scene(registry(), gltf_node_cache);
|
||||
|
||||
// Convert meshes
|
||||
auto mesh_view = registry().view<entt::resource<Mesh>>();
|
||||
for (auto [entity, mesh] : mesh_view.each()) {
|
||||
registry().emplace<GpuMesh>(entity, GpuMesh(mesh));
|
||||
|
||||
// Remove Mesh resource as it is no longer needed.
|
||||
registry().erase<entt::resource<Mesh>>(entity);
|
||||
}
|
||||
|
||||
// Convert materials
|
||||
auto material_view = registry().view<entt::resource<Material>>();
|
||||
for (auto [entity, material] : material_view.each()) {
|
||||
registry().emplace<GpuMaterial>(entity, GpuMaterial(material));
|
||||
|
||||
// Remove Material resource as it is no longer needed.
|
||||
registry().erase<entt::resource<Material>>(entity);
|
||||
}
|
||||
|
||||
// Spawn default lights
|
||||
auto directional_light = registry().create();
|
||||
registry().emplace<Name>(directional_light, "Directional Light");
|
||||
registry().emplace<Transform>(
|
||||
directional_light,
|
||||
Transform{.orientation = glm::toQuat(
|
||||
glm::lookAt({}, DirectionalLight::DEFAULT_DIRECTION, Camera::UP_VECTOR))});
|
||||
registry().emplace<GlobalTransform>(directional_light, GlobalTransform{});
|
||||
registry().emplace<DirectionalLight>(
|
||||
directional_light, DirectionalLight{.illuminance = DirectionalLight::DEFAULT_ILLUMINANCE});
|
||||
|
||||
auto point_light = registry().create();
|
||||
registry().emplace<Name>(point_light, "Point Light");
|
||||
registry().emplace<Transform>(point_light,
|
||||
Transform{.translation = PointLight::DEFAULT_POSITION});
|
||||
registry().emplace<GlobalTransform>(point_light, GlobalTransform{});
|
||||
registry().emplace<PointLight>(point_light,
|
||||
PointLight{.intensity = PointLight::DEFAULT_INTENSITY});
|
||||
}
|
||||
|
||||
void Controller::update()
|
||||
{
|
||||
Camera::keyboard_movement(registry());
|
||||
|
||||
if (registry().ctx().get<Window::MouseCatched>().catched) {
|
||||
Camera::mouse_orientation(registry());
|
||||
}
|
||||
}
|
||||
16
src/bin/controller.h
Normal file
16
src/bin/controller.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/game_loop.h"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Controller : public GameLoop
|
||||
{
|
||||
public:
|
||||
Controller();
|
||||
void update() override;
|
||||
};
|
||||
1
src/bin/debug.cpp
Normal file
1
src/bin/debug.cpp
Normal file
@@ -0,0 +1 @@
|
||||
// todo camera movement here ...
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Controller.h"
|
||||
#include "controller.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
struct Transform
|
||||
{
|
||||
glm::vec3 translation;
|
||||
glm::quat orientation;
|
||||
glm::vec3 translation{};
|
||||
glm::quat orientation{};
|
||||
glm::vec3 scale{1.0, 1.0, 1.0};
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
auto Camera::projection_matrix() const -> glm::mat4
|
||||
{
|
||||
@@ -45,14 +46,20 @@ void Camera::keyboard_movement(entt::registry& registry)
|
||||
|
||||
auto camera_view = registry.view<Camera const, Transform, GlobalTransform const>();
|
||||
auto camera_entity = camera_view.front();
|
||||
|
||||
if (camera_entity == entt::null) {
|
||||
spdlog::debug("No camera entity found");
|
||||
return;
|
||||
}
|
||||
|
||||
auto [camera, camera_transform, camera_global_transform] = camera_view.get(camera_entity);
|
||||
|
||||
glm::vec3 front_vec = front_vector(camera_global_transform);
|
||||
front_vec.y = 0;
|
||||
|
||||
glm::vec3 delta_pos = glm::vec3(0., 0., 0.);
|
||||
float delta_factor =
|
||||
SPEED * delta_time.delta.count() * (movement_context.accelerate ? ACCELERATION : 1.0F);
|
||||
float acceleration = movement_context.accelerate ? ACCELERATION : 1.0F;
|
||||
float delta_factor = static_cast<float>(delta_time.delta.count()) * SPEED * acceleration;
|
||||
movement_context.accelerate = false;
|
||||
|
||||
if (key_state.pressed(Input::KeyCode{GLFW_KEY_W})) {
|
||||
@@ -84,6 +91,12 @@ void Camera::mouse_orientation(entt::registry& registry)
|
||||
{
|
||||
auto camera_view = registry.view<Camera, Transform>();
|
||||
auto camera_entity = camera_view.front();
|
||||
|
||||
if (camera_entity == entt::null) {
|
||||
spdlog::debug("No camera entity found");
|
||||
return;
|
||||
}
|
||||
|
||||
auto [camera, camera_transform] = camera_view.get(camera_entity);
|
||||
|
||||
auto const& mouse_cursor_input = registry.ctx().get<Input::MouseMotion>();
|
||||
@@ -113,6 +126,12 @@ void Camera::aspect_ratio_update(entt::registry& registry)
|
||||
|
||||
auto camera_view = registry.view<Camera>();
|
||||
auto camera_entity = camera_view.front();
|
||||
|
||||
if (camera_entity == entt::null) {
|
||||
spdlog::debug("No camera entity found");
|
||||
return;
|
||||
}
|
||||
|
||||
auto [camera] = camera_view.get(camera_entity);
|
||||
|
||||
// Orthographic projection currently unsupported
|
||||
|
||||
104
src/core/game_loop.cpp
Normal file
104
src/core/game_loop.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "game_loop.h"
|
||||
#include "core/camera.h"
|
||||
#include "core/light.h"
|
||||
#include "core/render.h"
|
||||
#include "core/shader.h"
|
||||
#include "core/time.h"
|
||||
#include "input/input.h"
|
||||
#include "scene/scene.h"
|
||||
#include "window/window.h"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fx/gltf.h>
|
||||
#include <glad/gl.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
GameLoop::~GameLoop() = default;
|
||||
|
||||
GameLoop::GameLoop() :
|
||||
game_window(std::make_shared<Window>(event_dispatcher)),
|
||||
post_processing_framebuffer(game_window->physical_dimensions()),
|
||||
key_listener{.registry = entt_registry},
|
||||
cursor_listener{.registry = entt_registry},
|
||||
gltf_loader{.image_cache = image_cache,
|
||||
.material_cache = material_cache,
|
||||
.mesh_cache = mesh_cache,
|
||||
.shader_cache = shader_cache,
|
||||
.gltf_mesh_cache = gltf_mesh_cache,
|
||||
.gltf_node_cache = gltf_node_cache},
|
||||
gltf_cache(gltf_loader)
|
||||
{
|
||||
register_context_variables();
|
||||
|
||||
event_dispatcher.sink<Window::ResizeEvent>().connect<&GameLoop::recreate_framebuffer>(this);
|
||||
event_dispatcher.sink<Input::KeyInput>().connect<&Input::KeyListener::key_event>(key_listener);
|
||||
event_dispatcher.sink<Input::MouseMotion>().connect<&Input::CursorListener::cursor_event>(
|
||||
cursor_listener);
|
||||
}
|
||||
|
||||
void GameLoop::run()
|
||||
{
|
||||
entt::hashed_string shader_hash(Material::SHADER_NAME.data());
|
||||
auto standard_material_shader =
|
||||
shader_cache.load(shader_hash, Material::SHADER_NAME).first->second;
|
||||
|
||||
spdlog::info("Startup complete. Enter game loop.");
|
||||
|
||||
// This is the game loop
|
||||
while (glfwWindowShouldClose(&game_window->handle()) == GLFW_FALSE) {
|
||||
// --- Timing ---
|
||||
Time::update_delta_time(entt_registry);
|
||||
|
||||
// --- Check events, handle input ---
|
||||
glfwPollEvents();
|
||||
|
||||
// --- Update game state ---
|
||||
event_dispatcher.update();
|
||||
|
||||
game_window->update_descriptor(entt_registry);
|
||||
game_window->mouse_catching(entt_registry);
|
||||
game_window->close_on_esc(entt_registry);
|
||||
|
||||
GlobalTransform::update(entt_registry);
|
||||
Camera::aspect_ratio_update(entt_registry);
|
||||
|
||||
update();
|
||||
|
||||
Input::State<Input::KeyCode>::update_state(entt_registry);
|
||||
Input::reset_mouse_motion(entt_registry);
|
||||
|
||||
// --- Render and buffer swap ---
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
post_processing_framebuffer.bind();
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
Light::update_lights(entt_registry, standard_material_shader);
|
||||
Render::render(entt_registry);
|
||||
|
||||
Framebuffer::unbind();
|
||||
post_processing_framebuffer.draw(post_processing_shader);
|
||||
|
||||
glfwSwapBuffers(&game_window->handle());
|
||||
}
|
||||
}
|
||||
|
||||
void GameLoop::register_context_variables()
|
||||
{
|
||||
entt_registry.ctx().emplace<Input::State<Input::KeyCode>>();
|
||||
entt_registry.ctx().emplace<Input::MouseMotion>();
|
||||
}
|
||||
|
||||
void GameLoop::recreate_framebuffer()
|
||||
{
|
||||
auto dimensions = game_window->physical_dimensions();
|
||||
post_processing_framebuffer = Framebuffer(dimensions);
|
||||
}
|
||||
65
src/core/game_loop.h
Normal file
65
src/core/game_loop.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/graphics/framebuffer.h"
|
||||
#include "core/shader.h"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "entt/signal/fwd.hpp"
|
||||
#include "input/input.h"
|
||||
#include "scene/gltf_loader.h"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Camera;
|
||||
class Window;
|
||||
|
||||
class GameLoop
|
||||
{
|
||||
public:
|
||||
GameLoop();
|
||||
|
||||
virtual ~GameLoop();
|
||||
GameLoop(GameLoop const&) = delete;
|
||||
GameLoop(GameLoop&&) = delete;
|
||||
auto operator=(GameLoop const&) -> GameLoop& = delete;
|
||||
auto operator=(GameLoop&&) -> GameLoop& = delete;
|
||||
|
||||
auto registry() -> entt::registry& { return entt_registry; }
|
||||
auto registry() const -> entt::registry const& { return entt_registry; }
|
||||
|
||||
auto dispatcher() -> entt::dispatcher& { return event_dispatcher; }
|
||||
auto dispatcher() const -> entt::dispatcher const& { return event_dispatcher; }
|
||||
|
||||
void run();
|
||||
|
||||
virtual void update() = 0;
|
||||
|
||||
protected:
|
||||
virtual void register_context_variables();
|
||||
void recreate_framebuffer();
|
||||
|
||||
std::shared_ptr<Window> game_window;
|
||||
|
||||
Shader post_processing_shader{"post_processing", "data/shaders"};
|
||||
Framebuffer post_processing_framebuffer;
|
||||
|
||||
entt::registry entt_registry;
|
||||
|
||||
entt::dispatcher event_dispatcher{};
|
||||
Input::KeyListener key_listener;
|
||||
Input::CursorListener cursor_listener;
|
||||
|
||||
// Resource caches
|
||||
entt::resource_cache<Image> image_cache;
|
||||
entt::resource_cache<Material> material_cache;
|
||||
entt::resource_cache<Mesh> mesh_cache;
|
||||
entt::resource_cache<Shader, ShaderLoader> shader_cache;
|
||||
entt::resource_cache<GltfMesh> gltf_mesh_cache;
|
||||
entt::resource_cache<GltfNode> gltf_node_cache;
|
||||
|
||||
GltfLoader gltf_loader;
|
||||
entt::resource_cache<Gltf, GltfLoader> gltf_cache;
|
||||
};
|
||||
@@ -53,6 +53,8 @@ struct GpuMesh
|
||||
|
||||
auto operator=(GpuMesh &&other) noexcept -> GpuMesh &
|
||||
{
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
|
||||
vao = other.vao;
|
||||
indices_count = other.indices_count;
|
||||
indices_type = other.indices_type;
|
||||
|
||||
@@ -4,11 +4,19 @@
|
||||
#include "core/graphics/mesh.h"
|
||||
#include "core/shader.h"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
void Render::render(entt::registry& registry)
|
||||
{
|
||||
auto mesh_view = registry.view<GpuMesh const, GpuMaterial const, GlobalTransform const>();
|
||||
auto camera_view = registry.view<Camera const, GlobalTransform const>();
|
||||
auto camera_entity = camera_view.front();
|
||||
|
||||
if (camera_entity == entt::null) {
|
||||
spdlog::debug("No camera entity found");
|
||||
return;
|
||||
}
|
||||
|
||||
auto [camera, camera_transform] = camera_view.get(camera_entity);
|
||||
glm::mat4 view_projection_matrix =
|
||||
camera.projection_matrix() * Camera::view_matrix(camera_transform);
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Input {
|
||||
|
||||
void KeyListener::key_event(KeyInput const& key_input_event)
|
||||
{
|
||||
auto& key_state = registry.ctx().emplace<State<KeyCode>>();
|
||||
auto& key_state = registry.ctx().get<State<KeyCode>>();
|
||||
|
||||
if (key_input_event.action == static_cast<Action>(GLFW_PRESS)) {
|
||||
key_state.press(key_input_event.key_code);
|
||||
@@ -17,21 +17,16 @@ void KeyListener::key_event(KeyInput const& key_input_event)
|
||||
|
||||
void CursorListener::cursor_event(MouseMotion const& mouse_motion_event)
|
||||
{
|
||||
auto& mouse_motion = registry.ctx().emplace<MouseMotion>();
|
||||
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().emplace<MouseMotion>();
|
||||
auto& mouse_motion = registry.ctx().get<MouseMotion>();
|
||||
mouse_motion = {};
|
||||
}
|
||||
|
||||
template <typename T> void State<T>::init_state(entt::registry& registry)
|
||||
{
|
||||
registry.ctx().emplace<State<T>>();
|
||||
}
|
||||
|
||||
template <typename T> void State<T>::update_state(entt::registry& registry)
|
||||
{
|
||||
auto& state = registry.ctx().get<State<T>>();
|
||||
|
||||
@@ -34,7 +34,6 @@ public:
|
||||
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); }
|
||||
|
||||
static void init_state(entt::registry& registry);
|
||||
static void update_state(entt::registry& registry);
|
||||
|
||||
private:
|
||||
|
||||
0
src/post_processing/post_processing.cpp
Normal file
0
src/post_processing/post_processing.cpp
Normal file
2
src/post_processing/post_processing.h
Normal file
2
src/post_processing/post_processing.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
|
||||
124
src/scene/gltf.cpp
Normal file
124
src/scene/gltf.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "gltf.h"
|
||||
#include "components/name.h"
|
||||
#include "components/relationship.h"
|
||||
#include "core/camera.h"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
auto Gltf::spawn_scene(std::size_t index,
|
||||
entt::registry& registry,
|
||||
entt::resource_cache<GltfNode>& node_cache) -> entt::entity
|
||||
{
|
||||
if (document.scenes.size() <= index) {
|
||||
return entt::null;
|
||||
}
|
||||
|
||||
auto& gltf_scene = document.scenes.at(index);
|
||||
|
||||
entt::entity scene_entity = registry.create();
|
||||
registry.emplace<Transform>(scene_entity, Transform{});
|
||||
registry.emplace<GlobalTransform>(scene_entity, GlobalTransform{});
|
||||
registry.emplace<Children>(scene_entity, Children{});
|
||||
|
||||
std::vector<entt::resource<GltfNode>> nodes;
|
||||
nodes.reserve(gltf_scene.nodes.size());
|
||||
|
||||
for (auto node_id : gltf_scene.nodes) {
|
||||
auto const& node = document.nodes.at(node_id);
|
||||
entt::hashed_string node_hash(node.name.c_str());
|
||||
nodes.push_back(node_cache[node_hash]);
|
||||
}
|
||||
|
||||
if (gltf_scene.name.empty()) {
|
||||
spdlog::warn("glTF scene has no name.");
|
||||
}
|
||||
|
||||
// Spawn an entity for every node in scene
|
||||
for (auto const& node : nodes) {
|
||||
std::function<entt::entity(GltfNode const&, entt::entity)> spawn_node =
|
||||
[®istry, &spawn_node](GltfNode const& node, entt::entity parent) {
|
||||
auto entity = registry.create();
|
||||
registry.emplace<Name>(entity, node.name);
|
||||
registry.emplace<Transform>(entity, node.transform);
|
||||
registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
registry.emplace<Parent>(entity, Parent{.parent = parent});
|
||||
|
||||
std::vector<entt::entity> child_entities;
|
||||
|
||||
auto mesh = node.mesh;
|
||||
if (mesh.has_value()) {
|
||||
for (auto const& primitive : mesh.value()->primitives) {
|
||||
auto mesh_entity = registry.create();
|
||||
registry.emplace<Parent>(mesh_entity, Parent{.parent = entity});
|
||||
registry.emplace<Transform>(mesh_entity, Transform{});
|
||||
registry.emplace<GlobalTransform>(mesh_entity, GlobalTransform{});
|
||||
registry.emplace<entt::resource<Mesh>>(mesh_entity, primitive.mesh);
|
||||
registry.emplace<entt::resource<Material>>(mesh_entity, primitive.material);
|
||||
|
||||
child_entities.push_back(mesh_entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto camera = node.camera;
|
||||
if (camera.has_value()) {
|
||||
auto perspective =
|
||||
std::get<fx::gltf::Camera::Perspective>(camera.value().projection);
|
||||
Camera::Perspective camera_perspective{.fov = perspective.yfov,
|
||||
.aspect_ratio = perspective.aspectRatio,
|
||||
.near = perspective.znear,
|
||||
.far = perspective.zfar};
|
||||
registry.emplace<Camera>(entity, Camera{.projection = camera_perspective});
|
||||
}
|
||||
|
||||
// Spawn child nodes
|
||||
for (auto const& child : node.children) {
|
||||
auto child_entity = spawn_node(child, entity);
|
||||
child_entities.push_back(child_entity);
|
||||
}
|
||||
|
||||
registry.emplace<Children>(entity, Children{.children = child_entities});
|
||||
return entity;
|
||||
};
|
||||
|
||||
auto node_entity = spawn_node(node, scene_entity);
|
||||
registry.get<Children>(scene_entity).children.push_back(node_entity);
|
||||
}
|
||||
|
||||
auto camera_view = registry.view<Camera const>();
|
||||
if (camera_view.empty()) {
|
||||
// Spawn default camera
|
||||
auto entity = registry.create();
|
||||
registry.emplace<Name>(entity, "Camera");
|
||||
registry.emplace<Transform>(entity, Transform{.translation = Camera::DEFAULT_POSITION});
|
||||
registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
registry.emplace<Camera>(entity, Camera{.projection = Camera::Perspective{}});
|
||||
}
|
||||
|
||||
return scene_entity;
|
||||
}
|
||||
|
||||
auto Gltf::spawn_scene(std::string_view name,
|
||||
entt::registry& registry,
|
||||
entt::resource_cache<GltfNode>& node_cache) -> entt::entity
|
||||
{
|
||||
auto it = std::find_if(document.scenes.cbegin(), document.scenes.cend(), [name](auto& scene) {
|
||||
return scene.name == name;
|
||||
});
|
||||
|
||||
if (it != document.scenes.cend()) {
|
||||
auto index = std::distance(document.scenes.cbegin(), it);
|
||||
return spawn_scene(index, registry, node_cache);
|
||||
}
|
||||
|
||||
return entt::null;
|
||||
}
|
||||
|
||||
auto Gltf::spawn_default_scene(entt::registry& registry, entt::resource_cache<GltfNode>& node_cache)
|
||||
-> entt::entity
|
||||
{
|
||||
if (document.scene != -1) {
|
||||
return spawn_scene(document.scene, registry, node_cache);
|
||||
}
|
||||
|
||||
return entt::null;
|
||||
}
|
||||
@@ -9,11 +9,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class Mesh;
|
||||
class Scene;
|
||||
class Material;
|
||||
class Image;
|
||||
|
||||
struct GltfPrimitive
|
||||
{
|
||||
entt::resource<Mesh> mesh;
|
||||
@@ -44,8 +39,17 @@ struct Gltf
|
||||
std::vector<entt::resource<Material>> materials;
|
||||
std::vector<entt::resource<GltfMesh>> meshes;
|
||||
std::vector<entt::resource<GltfNode>> nodes;
|
||||
std::vector<entt::resource<Scene>> scenes;
|
||||
|
||||
std::optional<entt::resource<Scene>> default_scene;
|
||||
fx::gltf::Document document;
|
||||
|
||||
auto spawn_scene(std::size_t index,
|
||||
entt::registry& registry,
|
||||
entt::resource_cache<GltfNode>& node_cache) -> entt::entity;
|
||||
|
||||
auto spawn_scene(std::string_view name,
|
||||
entt::registry& registry,
|
||||
entt::resource_cache<GltfNode>& node_cache) -> entt::entity;
|
||||
|
||||
auto spawn_default_scene(entt::registry& registry, entt::resource_cache<GltfNode>& node_cache)
|
||||
-> entt::entity;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "components/name.h"
|
||||
#include "components/relationship.h"
|
||||
#include "core/camera.h"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "scene.h"
|
||||
|
||||
#include <iterator>
|
||||
@@ -388,107 +389,105 @@ auto GltfLoader::operator()(std::filesystem::path const& document_path) -> resul
|
||||
nodes.push_back(node.second);
|
||||
}
|
||||
|
||||
// Load scenes
|
||||
std::vector<entt::resource<Scene>> scenes;
|
||||
for (auto const& gltf_scene : gltf.scenes) {
|
||||
// Get nodes by hash
|
||||
std::vector<entt::resource<GltfNode>> nodes;
|
||||
nodes.reserve(gltf_scene.nodes.size());
|
||||
// // Load scenes
|
||||
// std::vector<entt::resource<Scene>> scenes;
|
||||
// for (auto const& gltf_scene : gltf.scenes) {
|
||||
// // Get nodes by hash
|
||||
// std::vector<entt::resource<GltfNode>> nodes;
|
||||
// nodes.reserve(gltf_scene.nodes.size());
|
||||
|
||||
for (auto node_id : gltf_scene.nodes) {
|
||||
auto const& node = gltf.nodes.at(node_id);
|
||||
entt::hashed_string node_hash(node.name.c_str());
|
||||
nodes.push_back(gltf_node_cache[node_hash]);
|
||||
}
|
||||
// for (auto node_id : gltf_scene.nodes) {
|
||||
// auto const& node = gltf.nodes.at(node_id);
|
||||
// entt::hashed_string node_hash(node.name.c_str());
|
||||
// nodes.push_back(gltf_node_cache[node_hash]);
|
||||
// }
|
||||
|
||||
if (gltf_scene.name.empty()) {
|
||||
spdlog::warn("glTF scene has no name.");
|
||||
}
|
||||
// if (gltf_scene.name.empty()) {
|
||||
// spdlog::warn("glTF scene has no name.");
|
||||
// }
|
||||
|
||||
// Spawn an entity for every node in scene
|
||||
for (auto const& node : nodes) {
|
||||
std::function<entt::entity(GltfNode const&, std::optional<entt::entity>)> spawn_node =
|
||||
[this, &spawn_node](GltfNode const& node, std::optional<entt::entity> parent) {
|
||||
auto entity = registry.create();
|
||||
registry.emplace<Name>(entity, node.name);
|
||||
registry.emplace<Transform>(entity, node.transform);
|
||||
registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
// // Spawn an entity for every node in scene
|
||||
// for (auto const& node : nodes) {
|
||||
// std::function<entt::entity(GltfNode const&, std::optional<entt::entity>)> spawn_node =
|
||||
// [this, &spawn_node](GltfNode const& node, std::optional<entt::entity> parent) {
|
||||
// auto entity = registry.create();
|
||||
// registry.emplace<Name>(entity, node.name);
|
||||
// registry.emplace<Transform>(entity, node.transform);
|
||||
// registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
|
||||
if (parent.has_value()) {
|
||||
registry.emplace<Parent>(entity, Parent{.parent = parent.value()});
|
||||
}
|
||||
// if (parent.has_value()) {
|
||||
// registry.emplace<Parent>(entity, Parent{.parent = parent.value()});
|
||||
// }
|
||||
|
||||
std::vector<entt::entity> child_entities;
|
||||
// std::vector<entt::entity> child_entities;
|
||||
|
||||
auto mesh = node.mesh;
|
||||
if (mesh.has_value()) {
|
||||
for (auto const& primitive : mesh.value()->primitives) {
|
||||
auto mesh_entity = registry.create();
|
||||
registry.emplace<Parent>(mesh_entity, Parent{.parent = entity});
|
||||
registry.emplace<Transform>(mesh_entity, Transform{});
|
||||
registry.emplace<GlobalTransform>(mesh_entity, GlobalTransform{});
|
||||
registry.emplace<entt::resource<Mesh>>(mesh_entity, primitive.mesh);
|
||||
registry.emplace<entt::resource<Material>>(mesh_entity,
|
||||
primitive.material);
|
||||
// auto mesh = node.mesh;
|
||||
// if (mesh.has_value()) {
|
||||
// for (auto const& primitive : mesh.value()->primitives) {
|
||||
// auto mesh_entity = registry.create();
|
||||
// registry.emplace<Parent>(mesh_entity, Parent{.parent = entity});
|
||||
// registry.emplace<Transform>(mesh_entity, Transform{});
|
||||
// registry.emplace<GlobalTransform>(mesh_entity, GlobalTransform{});
|
||||
// registry.emplace<entt::resource<Mesh>>(mesh_entity, primitive.mesh);
|
||||
// registry.emplace<entt::resource<Material>>(mesh_entity,
|
||||
// primitive.material);
|
||||
|
||||
child_entities.push_back(mesh_entity);
|
||||
}
|
||||
}
|
||||
// child_entities.push_back(mesh_entity);
|
||||
// }
|
||||
// }
|
||||
|
||||
auto camera = node.camera;
|
||||
if (camera.has_value()) {
|
||||
auto perspective =
|
||||
std::get<fx::gltf::Camera::Perspective>(camera.value().projection);
|
||||
Camera::Perspective camera_perspective{.fov = perspective.yfov,
|
||||
.aspect_ratio =
|
||||
perspective.aspectRatio,
|
||||
.near = perspective.znear,
|
||||
.far = perspective.zfar};
|
||||
registry.emplace<Camera>(entity, Camera{.projection = camera_perspective});
|
||||
}
|
||||
// auto camera = node.camera;
|
||||
// if (camera.has_value()) {
|
||||
// auto perspective =
|
||||
// std::get<fx::gltf::Camera::Perspective>(camera.value().projection);
|
||||
// Camera::Perspective camera_perspective{.fov = perspective.yfov,
|
||||
// .aspect_ratio =
|
||||
// perspective.aspectRatio,
|
||||
// .near = perspective.znear,
|
||||
// .far = perspective.zfar};
|
||||
// registry.emplace<Camera>(entity, Camera{.projection = camera_perspective});
|
||||
// }
|
||||
|
||||
// Spawn child nodes
|
||||
for (auto const& child : node.children) {
|
||||
auto child_entity = spawn_node(child, entity);
|
||||
child_entities.push_back(child_entity);
|
||||
}
|
||||
// // Spawn child nodes
|
||||
// for (auto const& child : node.children) {
|
||||
// auto child_entity = spawn_node(child, entity);
|
||||
// child_entities.push_back(child_entity);
|
||||
// }
|
||||
|
||||
registry.emplace<Children>(entity, Children{.children = child_entities});
|
||||
return entity;
|
||||
};
|
||||
// registry.emplace<Children>(entity, Children{.children = child_entities});
|
||||
// return entity;
|
||||
// };
|
||||
|
||||
spawn_node(node, {});
|
||||
}
|
||||
// spawn_node(node, {});
|
||||
// }
|
||||
|
||||
auto camera_view = registry.view<Camera const>();
|
||||
if (camera_view.empty()) {
|
||||
// Spawn default camera
|
||||
auto entity = registry.create();
|
||||
registry.emplace<Name>(entity, "Camera");
|
||||
registry.emplace<Transform>(entity, Transform{.translation = Camera::DEFAULT_POSITION});
|
||||
registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
registry.emplace<Camera>(entity, Camera{.projection = Camera::Perspective{}});
|
||||
}
|
||||
// auto camera_view = registry.view<Camera const>();
|
||||
// if (camera_view.empty()) {
|
||||
// // Spawn default camera
|
||||
// auto entity = registry.create();
|
||||
// registry.emplace<Name>(entity, "Camera");
|
||||
// registry.emplace<Transform>(entity, Transform{.translation = Camera::DEFAULT_POSITION});
|
||||
// registry.emplace<GlobalTransform>(entity, GlobalTransform{});
|
||||
// registry.emplace<Camera>(entity, Camera{.projection = Camera::Perspective{}});
|
||||
// }
|
||||
|
||||
entt::hashed_string scene_hash(gltf_scene.name.c_str());
|
||||
entt::resource<Scene> scene_resource =
|
||||
scene_cache.load(scene_hash, Scene{registry}).first->second;
|
||||
scenes.push_back(scene_resource);
|
||||
}
|
||||
// entt::hashed_string scene_hash(gltf_scene.name.c_str());
|
||||
// entt::resource<Scene> scene_resource =
|
||||
// scene_cache.load(scene_hash, Scene{}).first->second;
|
||||
// scenes.push_back(scene_resource);
|
||||
// }
|
||||
|
||||
// Default scene
|
||||
auto default_scene = [&gltf, &scenes]() -> std::optional<entt::resource<Scene>> {
|
||||
if (gltf.scene != -1) {
|
||||
return scenes.at(gltf.scene);
|
||||
}
|
||||
// // Default scene
|
||||
// auto default_scene = [&gltf, &scenes]() -> std::optional<entt::resource<Scene>> {
|
||||
// if (gltf.scene != -1) {
|
||||
// return scenes.at(gltf.scene);
|
||||
// }
|
||||
|
||||
return {};
|
||||
}();
|
||||
// return {};
|
||||
// }();
|
||||
|
||||
return std::make_shared<Gltf>(Gltf{.materials = std::move(materials),
|
||||
.meshes = std::move(gltf_meshes),
|
||||
.nodes = std::move(nodes),
|
||||
.scenes = std::move(scenes),
|
||||
.default_scene = default_scene,
|
||||
.document = std::move(gltf)});
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "gltf.h"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
@@ -19,10 +18,7 @@ struct GltfLoader
|
||||
entt::resource_cache<Material>& material_cache;
|
||||
entt::resource_cache<Mesh>& mesh_cache;
|
||||
entt::resource_cache<Shader, ShaderLoader>& shader_cache;
|
||||
entt::resource_cache<Scene>& scene_cache;
|
||||
|
||||
entt::resource_cache<GltfMesh>& gltf_mesh_cache;
|
||||
entt::resource_cache<GltfNode>& gltf_node_cache;
|
||||
|
||||
entt::registry& registry;
|
||||
};
|
||||
|
||||
@@ -1,57 +1,21 @@
|
||||
#include "scene.h"
|
||||
#include "components/name.h"
|
||||
#include "components/transform.h"
|
||||
#include "core/camera.h"
|
||||
#include "core/graphics/material.h"
|
||||
#include "core/graphics/mesh.h"
|
||||
#include "core/light.h"
|
||||
#include "window/window.h"
|
||||
|
||||
Scene::Scene(entt::registry& registry) : registry(registry)
|
||||
{
|
||||
auto mesh_view = registry.view<entt::resource<Mesh>>();
|
||||
for (auto [entity, mesh] : mesh_view.each()) {
|
||||
registry.emplace<GpuMesh>(entity, GpuMesh(mesh));
|
||||
// void Scene::spawn_scenes(entt::registry& registry)
|
||||
// {
|
||||
// auto scene_view = registry.view<entt::resource<Scene>>();
|
||||
// for (auto [entity, scene] : scene_view.each()) {
|
||||
// registry.erase<entt::resource<Scene>>(entity);
|
||||
|
||||
// Remove Mesh resource as it is no longer needed.
|
||||
registry.erase<entt::resource<Mesh>>(entity);
|
||||
}
|
||||
// for (auto [id, storage] : scene->registry.storage()) {
|
||||
// if (auto* other = registry.storage(id); other && storage.contains(entity)) {
|
||||
// other->push(copy, storage.value(entity));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
auto material_view = registry.view<entt::resource<Material>>();
|
||||
for (auto [entity, material] : material_view.each()) {
|
||||
registry.emplace<GpuMaterial>(entity, GpuMaterial(material));
|
||||
// Notes:
|
||||
// By loading a scene from disk, spawn it directly into the world.
|
||||
// This is less flexible but easier to implement
|
||||
|
||||
// Remove Material resource as it is no longer needed.
|
||||
registry.erase<entt::resource<Material>>(entity);
|
||||
}
|
||||
|
||||
// Spawn default lights
|
||||
auto directional_light = registry.create();
|
||||
registry.emplace<Name>(directional_light, "Directional Light");
|
||||
registry.emplace<Transform>(
|
||||
directional_light,
|
||||
Transform{.orientation = glm::toQuat(
|
||||
glm::lookAt({}, DirectionalLight::DEFAULT_DIRECTION, Camera::UP_VECTOR))});
|
||||
registry.emplace<GlobalTransform>(directional_light, GlobalTransform{});
|
||||
registry.emplace<DirectionalLight>(
|
||||
directional_light, DirectionalLight{.illuminance = DirectionalLight::DEFAULT_ILLUMINANCE});
|
||||
|
||||
auto point_light = registry.create();
|
||||
registry.emplace<Name>(point_light, "Point Light");
|
||||
registry.emplace<Transform>(point_light,
|
||||
Transform{.translation = PointLight::DEFAULT_POSITION});
|
||||
registry.emplace<GlobalTransform>(point_light, GlobalTransform{});
|
||||
registry.emplace<PointLight>(point_light,
|
||||
PointLight{.intensity = PointLight::DEFAULT_INTENSITY});
|
||||
}
|
||||
|
||||
void Scene::update()
|
||||
{
|
||||
GlobalTransform::update(registry);
|
||||
Camera::aspect_ratio_update(registry);
|
||||
Camera::keyboard_movement(registry);
|
||||
|
||||
if (registry.ctx().get<Window::MouseCatched>().catched) {
|
||||
Camera::mouse_orientation(registry);
|
||||
}
|
||||
}
|
||||
// Use bevy format: file.gltf#Scene0 otherwise default scene...
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
#include <chrono>
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
class Scene
|
||||
struct Scene
|
||||
{
|
||||
public:
|
||||
Scene(entt::registry& registry);
|
||||
// entt::registry registry;
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
entt::registry& registry;
|
||||
// Spawns a scene for every entity having an entt:resource<Scene> component
|
||||
// static void spawn_scenes(entt::registry& registry);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user