2#ifndef LIBSBX_SCENES_SCENE_HPP_
3#define LIBSBX_SCENES_SCENE_HPP_
14#include <unordered_map>
18#include <range/v3/all.hpp>
20#include <yaml-cpp/yaml.h>
22#include <libsbx/assets/assets_module.hpp>
24#include <libsbx/utility/hashed_string.hpp>
25#include <libsbx/utility/iterator.hpp>
27#include <libsbx/memory/tracking_allocator.hpp>
29#include <libsbx/containers/octree.hpp>
31#include <libsbx/ecs/registry.hpp>
32#include <libsbx/ecs/entity.hpp>
34#include <libsbx/math/uuid.hpp>
35#include <libsbx/math/vector3.hpp>
36#include <libsbx/math/quaternion.hpp>
40#include <libsbx/core/engine.hpp>
42#include <libsbx/graphics/graphics_module.hpp>
44#include <libsbx/graphics/images/image2d.hpp>
45#include <libsbx/graphics/images/cube_image.hpp>
47#include <libsbx/signals/signal.hpp>
49#include <libsbx/scenes/node.hpp>
50#include <libsbx/scenes/components/directional_light.hpp>
51#include <libsbx/scenes/components/id.hpp>
52#include <libsbx/scenes/components/tag.hpp>
53#include <libsbx/scenes/components/camera.hpp>
54#include <libsbx/scenes/components/transform.hpp>
55#include <libsbx/scenes/components/global_transform.hpp>
56#include <libsbx/scenes/components/static_mesh.hpp>
57#include <libsbx/scenes/components/skinned_mesh.hpp>
59namespace sbx::scenes {
65template<
typename Component>
74 static constexpr auto csm_cascade_count = std::uint32_t{4u};
77 std::array<math::matrix4x4, csm_cascade_count> light_spaces{};
91 template<
typename... Exclude>
92 inline static constexpr auto query_filter = ecs::exclude<Exclude...>;
94 scene(
const std::filesystem::path& path);
96 virtual ~scene() =
default;
102 auto destroy_node(
const scenes::node
node) -> void;
104 auto camera() -> scenes::node {
108 auto set_active_camera(
const scenes::node
camera) ->
void {
124 auto make_child_of(
const scenes::node
node,
const scenes::node parent) -> void;
126 auto is_valid(
const scenes::node
node)
const ->
bool {
127 return _registry.is_valid(
node);
130 template<
typename Type,
typename... Other,
typename... Exclude>
132 return _registry.view<Type, Other...>(ecs::exclude<Exclude...>);
135 template<
typename Type,
typename... Other,
typename... Exclude>
137 return _registry.view<Type, Other...>(ecs::exclude<Exclude...>);
140 template<
typename Type,
typename Compare,
typename Sort =
utility::std_sort,
typename... Args>
141 auto sort(Compare compare, Sort sort = Sort{}, Args&&... args) ->
void {
142 _registry.sort<Type>(std::move(compare), std::move(sort), std::forward<Args>(args)...);
145 template<
typename Component>
146 auto has_component(
const scenes::node
node)
const ->
bool {
147 return _registry.all_of<Component>(
node);
150 template<
typename Component,
typename... Args>
151 auto add_component(
const scenes::node
node, Args&&... args) ->
decltype(
auto) {
152 return _registry.emplace<Component>(
node, std::forward<Args>(args)...);
155 template<
typename Component>
156 auto get_component(
const scenes::node
node)
const ->
const Component& {
157 return _registry.get<Component>(
node);
160 template<
typename Component>
161 auto get_component(
const scenes::node
node) -> Component& {
162 return _registry.get<Component>(
node);
165 template<
typename Component,
typename... Args>
166 auto get_or_add_component(
const scenes::node
node, Args&&... args) -> Component& {
167 return _registry.get_or_emplace<Component>(
node, std::forward<Args>(args)...);
174 auto root() -> scenes::node {
179 const auto&
camera = get_component<scenes::camera>(_camera);
181 const auto camera_view = math::matrix4x4::inverted(world_transform(_camera));
182 const auto camera_projection =
camera.projection(0.1f, 100.0f);
184 const auto inverse_view_projection = math::matrix4x4::inverted(camera_projection * camera_view);
186 static constexpr auto frustum_corners_clip = std::array<math::vector4, 8u>{
197 auto frustum_corners_world = std::array<math::vector3, 8u>{};
199 for (
auto i = 0u; i < 8u; i++) {
200 auto corner_world = inverse_view_projection * frustum_corners_clip[i];
202 corner_world /= corner_world.w();
206 auto center = math::vector3::zero;
208 for (
const auto& corner : frustum_corners_world) {
214 const auto light_dir = math::vector3::normalized(_light.direction());
217 auto up = math::vector3::up;
219 if (std::abs(math::vector3::dot(light_dir, up)) > 0.99f) {
224 const auto light_view_initial = math::matrix4x4::look_at(center, center + light_dir, up);
226 auto min_z = std::numeric_limits<std::float_t>::max();
227 auto max_z = std::numeric_limits<std::float_t>::lowest();
229 for (
const auto& corner : frustum_corners_world) {
230 const auto p = light_view_initial *
math::vector4{corner, 1.0f};
231 min_z = std::min(min_z, p.z());
232 max_z = std::max(max_z, p.z());
236 static constexpr auto z_caster_padding = 50.0f;
237 const auto pull_back = std::abs(min_z) + z_caster_padding;
239 const auto light_position = center - light_dir * pull_back;
242 const auto light_view = math::matrix4x4::look_at(light_position, center, up);
244 auto min_bounds =
math::vector3{std::numeric_limits<std::float_t>::max()};
245 auto max_bounds =
math::vector3{std::numeric_limits<std::float_t>::lowest()};
247 for (
const auto& corner : frustum_corners_world) {
248 const auto corner_light_space = light_view *
math::vector4{corner, 1.0f};
256 const auto z_range = max_bounds.z() - min_bounds.z();
257 const auto z_padding = std::max(z_range * 2.0f, 50.0f);
259 auto near_plane = -(max_bounds.z() + z_padding);
260 auto far_plane = -(min_bounds.z() - z_padding);
262 if (near_plane > far_plane) {
263 std::swap(near_plane, far_plane);
267 static constexpr auto xy_padding = 10.0f;
269 const auto light_projection = math::matrix4x4::orthographic(
270 min_bounds.x() - xy_padding, max_bounds.x() + xy_padding,
271 min_bounds.y() - xy_padding, max_bounds.y() + xy_padding,
272 near_plane, far_plane
275 return light_projection * light_view;
279 auto find_node(
const scenes::id&
id) -> scenes::node {
280 if (
auto entry = _nodes.find(
id); entry != _nodes.end()) {
281 return entry->second;
284 return scenes::node::null;
287 auto save(
const std::filesystem::path& path)-> void;
289 template<
typename... Args>
290 auto add_image(
const utility::hashed_string& name,
const std::filesystem::path& path, Args&&... args) ->
void {
291 auto& graphics_module = sbx::core::engine::get_module<sbx::graphics::graphics_module>();
293 const auto id = graphics_module.add_resource<
graphics::image2d>(path, std::forward<Args>(args)...);
295 _image_ids.emplace(name,
id);
300 return _image_ids.find(name) != _image_ids.end();
304 if (
auto entry = _image_ids.find(name); entry != _image_ids.end()) {
305 return entry->second;
312 return _image_metadata.at(handle);
315 template<
typename... Args>
316 auto add_cube_image(
const utility::hashed_string& name,
const std::filesystem::path& path, Args&&... args) ->
void {
317 auto& graphics_module = sbx::core::engine::get_module<sbx::graphics::graphics_module>();
319 const auto id = graphics_module.add_resource<
graphics::cube_image>(path, std::forward<Args>(args)...);
321 _cube_image_ids.emplace(name,
id);
326 return _cube_image_ids.at(name);
330 return _cube_image_metadata.at(handle);
333 template<
typename Mesh,
typename... Args>
335 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
337 const auto id = assets_module.add_asset<Mesh>(std::forward<Args>(args)...);
339 _mesh_ids.emplace(name,
id);
343 template<
typename Mesh,
typename Path,
typename... Args>
344 requires (std::is_constructible_v<std::filesystem::path, Path>)
346 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
349 const auto id = assets_module.add_asset<Mesh>(path, std::forward<Args>(args)...);
351 _mesh_ids.emplace(name,
id);
356 return _mesh_ids.at(name);
360 return _mesh_metadata.at(handle);
363 template<
typename Mesh,
typename... Args>
365 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
367 _animation_ids.emplace(name, assets_module.add_asset<Mesh>(std::forward<Args>(args)...));
371 return _animation_ids.at(name);
374 template<
typename Material,
typename... Args>
376 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
378 const auto id = assets_module.add_asset<Material>(std::forward<Args>(args)...);
380 _materials_ids.emplace(name,
id);
383 return assets_module.get_asset<Material>(
id);
387 return _materials_ids.find(name) != _materials_ids.end();
391 return _materials_ids.at(name);
395 return _material_metadata.at(handle);
399 return _uniform_handler;
402 auto update_uniform_handler() ->
void {
403 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
405 auto&
camera = get_component<scenes::camera>(_camera);
407 const auto& projection =
camera.projection();
409 _uniform_handler.push(
"projection", projection);
410 _uniform_handler.push(
"inverse_projection", math::matrix4x4::inverted(projection));
412 auto inverse_view = world_transform(_camera);
414 const auto view = math::matrix4x4::inverted(inverse_view);
418 _uniform_handler.push(
"view", view);
419 _uniform_handler.push(
"inverse_view", inverse_view);
421 _uniform_handler.push(
"viewport", graphics_module.viewport());
423 _uniform_handler.push(
"camera_position", world_position(_camera));
424 _uniform_handler.push(
"camera_near",
camera.near_plane());
425 _uniform_handler.push(
"camera_far",
camera.far_plane());
426 _uniform_handler.push(
"camera_fov_radians",
camera.field_of_view().to_radians().value());
428 const auto& camera_transform = get_component<scenes::transform>(_camera);
430 const auto camera_rotation = math::matrix_cast<math::matrix4x4>(world_rotation(_camera));
434 _uniform_handler.push(
"camera_right", camera_right);
435 _uniform_handler.push(
"camera_up", camera_up);
437 const auto csm = _build_csm();
439 _uniform_handler.push(
"light_spaces", csm.light_spaces);
440 _uniform_handler.push(
"cascade_splits", csm.cascade_splits);
442 _uniform_handler.push(
"light_direction", sbx::math::vector3::normalized(_light.direction()));
443 _uniform_handler.push(
"light_color", _light.color());
445 _uniform_handler.push(
"time", core::engine::time().value());
449 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
450 const auto& viewport = graphics_module.viewport();
452 const auto&
camera = get_component<scenes::camera>(_camera);
454 const auto world = world_transform(_camera);
455 const auto view = math::matrix4x4::inverted(world);
456 const auto projection =
camera.projection();
458 const auto inverse_view_projection = math::matrix4x4::inverted(projection * view);
460 const auto px = position.x() + 0.5f;
461 const auto py = position.y() + 0.5f;
463 const auto x = (2.0f * px) / viewport.x() - 1.0f;
464 const auto y = (2.0f * py) / viewport.y() - 1.0f;
469 auto near_world = inverse_view_projection * near_clip;
470 near_world /= near_world.w();
472 auto far_world = inverse_view_projection * far_clip;
473 far_world /= far_world.w();
475 const auto origin =
math::vector3{near_world.x(), near_world.y(), near_world.z()};
476 const auto direction = math::vector3::normalized(
math::vector3{far_world.x(), far_world.y(), far_world.z()} - origin);
481 auto node_count()
const -> std::size_t {
482 return _nodes.size();
487 static auto _lerp_float(
const std::float_t a,
const std::float_t b,
const std::float_t t) -> std::float_t {
488 return a + (b - a) * t;
491 static auto _compute_csm_splits(
const std::float_t near_plane,
const std::float_t far_plane,
const std::float_t lambda) -> std::array<std::float_t, csm_cascade_count> {
492 auto splits = std::array<std::float_t, csm_cascade_count>{};
494 for (
auto i = 0u; i < csm_cascade_count; ++i) {
495 const auto p =
static_cast<std::float_t
>(i + 1u) /
static_cast<std::float_t
>(csm_cascade_count);
497 const auto log_split = near_plane * std::pow(far_plane / near_plane, p);
498 const auto uni_split = near_plane + (far_plane - near_plane) * p;
500 splits[i] = _lerp_float(uni_split, log_split, lambda);
507 const auto camera_view = math::matrix4x4::inverted(camera_world);
508 const auto camera_projection =
camera.projection(slice_near, slice_far);
510 const auto inv_view_projection = math::matrix4x4::inverted(camera_projection * camera_view);
512 static constexpr auto frustum_corners_clip = std::array<math::vector4, 8u>{
523 auto corners_world = std::array<math::vector3, 8u>{};
524 auto center_world = math::vector3::zero;
526 for (
auto i = 0u; i < 8u; ++i) {
527 auto corner_world = inv_view_projection * frustum_corners_clip[i];
528 corner_world /= corner_world.w();
531 center_world += corners_world[i];
534 center_world /= 8.0f;
536 const auto light_dir = math::vector3::normalized(light_direction);
538 auto up = math::vector3::up;
540 if (std::abs(math::vector3::dot(light_dir, up)) > 0.99f) {
545 const auto light_view_initial = math::matrix4x4::look_at(center_world, center_world + light_dir, up);
547 auto min_z = std::numeric_limits<std::float_t>::max();
548 auto max_z = std::numeric_limits<std::float_t>::lowest();
550 for (
const auto& corner : corners_world) {
551 const auto p = light_view_initial *
math::vector4{corner, 1.0f};
552 min_z = std::min(min_z, p.z());
553 max_z = std::max(max_z, p.z());
557 static constexpr auto z_caster_padding = 50.0f;
558 const auto pull_back = std::abs(min_z) + z_caster_padding;
560 const auto light_position = center_world - light_dir * pull_back;
561 const auto light_view = math::matrix4x4::look_at(light_position, center_world, up);
564 auto min_bounds =
math::vector3{std::numeric_limits<std::float_t>::max()};
565 auto max_bounds =
math::vector3{std::numeric_limits<std::float_t>::lowest()};
567 for (
const auto& corner : corners_world) {
576 static constexpr auto xy_padding = 10.0f;
577 const auto extent_x = max_bounds.x() - min_bounds.x();
578 const auto extent_y = max_bounds.y() - min_bounds.y();
579 const auto extent = std::max(extent_x, extent_y) + 2.0f * xy_padding;
581 const auto texel_size = extent /
static_cast<std::float_t
>(shadow_resolution);
583 const auto center_ls = (min_bounds + max_bounds) * 0.5f;
585 auto min_x = center_ls.x() - extent * 0.5f;
586 auto min_y = center_ls.y() - extent * 0.5f;
588 min_x = std::floor(min_x / texel_size) * texel_size;
589 min_y = std::floor(min_y / texel_size) * texel_size;
591 const auto max_x = min_x + texel_size *
static_cast<std::float_t
>(shadow_resolution);
592 const auto max_y = min_y + texel_size *
static_cast<std::float_t
>(shadow_resolution);
595 const auto z_range = max_bounds.z() - min_bounds.z();
596 const auto z_padding = std::max(z_range * 2.0f, 50.0f);
598 auto near_plane = -(max_bounds.z() + z_padding);
599 auto far_plane = -(min_bounds.z() - z_padding);
601 if (near_plane > far_plane) {
602 std::swap(near_plane, far_plane);
605 const auto light_projection = math::matrix4x4::orthographic(
608 near_plane, far_plane
611 return light_projection * light_view;
614 auto _build_csm() -> csm_data {
615 const auto&
camera = get_component<scenes::camera>(_camera);
616 const auto camera_world = world_transform(_camera);
618 const auto camera_near =
camera.near_plane();
619 const auto camera_far =
camera.far_plane();
621 static constexpr auto lambda = 0.65f;
622 static constexpr auto shadow_resolution = std::uint32_t{2048u};
624 const auto splits = _compute_csm_splits(camera_near, camera_far, lambda);
626 auto result = csm_data{};
628 auto slice_near = camera_near;
630 for (
auto i = 0u; i < csm_cascade_count; ++i) {
631 const auto slice_far = splits[i];
633 result.light_spaces[i] = _build_light_space_for_slice(
camera, camera_world, _light.direction(), slice_near, slice_far, shadow_resolution);
635 slice_near = slice_far;
638 result.cascade_splits =
math::vector4{splits[0], splits[1], splits[2], camera_far};
643 auto _save_assets(YAML::Emitter& emitter) -> void;
645 auto _save_meshes(YAML::Emitter& emitter) -> void;
647 auto _save_textures(YAML::Emitter& emitter) -> void;
649 auto _save_node(YAML::Emitter& emitter,
const scenes::node
node) -> void;
651 auto _save_components(YAML::Emitter& emitter,
const scenes::node
node) -> void;
653 auto _load_assets(
const YAML::Node& assets) -> void;
655 auto _load_nodes(
const YAML::Node& nodes) -> void;
659 std::unordered_map<math::uuid, scenes::node> _nodes;
663 scenes::node _camera;
673 std::unordered_map<utility::hashed_string, graphics::image2d_handle> _image_ids;
674 std::unordered_map<utility::hashed_string, graphics::cube_image2d_handle> _cube_image_ids;
675 std::unordered_map<utility::hashed_string, math::uuid> _mesh_ids;
676 std::unordered_map<utility::hashed_string, math::uuid> _animation_ids;
677 std::unordered_map<utility::hashed_string, math::uuid> _materials_ids;
679 std::unordered_map<graphics::image2d_handle, assets::asset_metadata> _image_metadata;
680 std::unordered_map<graphics::cube_image2d_handle, assets::asset_metadata> _cube_image_metadata;
681 std::unordered_map<math::uuid, assets::asset_metadata> _mesh_metadata;
682 std::unordered_map<math::uuid, assets::asset_metadata> _material_metadata;
688 std::function<void(YAML::Emitter&,
scene&
scene,
const node)> save;
689 std::function<void(
const YAML::Node&,
scene&
scene,
const node)> load;
696 template<
typename Type, std::invocable<YAML::Emitter&, scene&, const Type&> Save, std::invocable<YAML::Node&> Load>
697 auto register_component(
const std::string& name, Save&& save, Load&& load) ->
void {
704 .save = [name, s = std::forward<Save>(save)](YAML::Emitter& yaml,
scene&
scene,
const node node) ->
void {
705 const auto& component =
scene.get_component<Type>(
node);
707 std::invoke(s, yaml,
scene, component);
709 .load = [name, l = std::forward<Load>(load)](
const YAML::Node& yaml,
scene&
scene,
const node node) ->
void {
710 scene.add_component<Type>(
node, std::invoke(l, yaml));
716 return _by_id.at(
id);
719 auto has(
const std::uint32_t
id) ->
bool {
720 return _by_id.contains(
id);
724 return _by_id.at(_by_name.at(name));
729 std::unordered_map<std::uint32_t, component_io> _by_id;
730 std::unordered_map<std::string, std::uint32_t> _by_name;
Definition: cube_image.hpp:21
Definition: image2d.hpp:50
Definition: resource_storage.hpp:18
Definition: matrix4x4.hpp:26
Definition: quaternion.hpp:25
A vector in two-dimensional space.
Definition: vector2.hpp:28
Definition: vector3.hpp:23
Definition: vector4.hpp:24
static constexpr auto min(const basic_vector &vector) noexcept -> value_type
Returns the minimum component of a vector.
Definition: vector.ipp:20
static constexpr auto max(const basic_vector &vector) noexcept -> value_type
Returns the maximum component of a vector.
Definition: vector.ipp:45
3D ray with normalized direction.
Definition: ray.hpp:37
Definition: camera.hpp:96
Definition: scene.hpp:692
Definition: directional_light.hpp:10
Definition: hashed_string.hpp:17
Matrix casting and decomposition utilities.
Ray type for geometric queries.
Definition: scene.hpp:686
Definition: exception.hpp:18
static auto value() noexcept -> std::uint32_t
Generates a unique ID for the type.
Definition: type_id.hpp:41
Definition: algorithm.hpp:13