sandbox
Loading...
Searching...
No Matches
scene.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_SCENES_SCENE_HPP_
3#define LIBSBX_SCENES_SCENE_HPP_
4
5#include <algorithm>
6#include <cmath>
7#include <filesystem>
8#include <functional>
9#include <memory>
10#include <numbers>
11#include <ranges>
12#include <ranges>
13#include <typeindex>
14#include <unordered_map>
15#include <utility>
16#include <vector>
17
18#include <range/v3/all.hpp>
19
20#include <yaml-cpp/yaml.h>
21
22#include <libsbx/assets/assets_module.hpp>
23
24#include <libsbx/utility/hashed_string.hpp>
25#include <libsbx/utility/iterator.hpp>
26
27#include <libsbx/memory/tracking_allocator.hpp>
28
29#include <libsbx/containers/octree.hpp>
30
31#include <libsbx/ecs/registry.hpp>
32#include <libsbx/ecs/entity.hpp>
33
34#include <libsbx/math/uuid.hpp>
35#include <libsbx/math/vector3.hpp>
36#include <libsbx/math/quaternion.hpp>
38#include <libsbx/math/ray.hpp>
39
40#include <libsbx/core/engine.hpp>
41
42#include <libsbx/graphics/graphics_module.hpp>
43
44#include <libsbx/graphics/images/image2d.hpp>
45#include <libsbx/graphics/images/cube_image.hpp>
46
47#include <libsbx/signals/signal.hpp>
48
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>
58
59namespace sbx::scenes {
60
62 scenes::node node;
63}; // struct node_destroyed_event
64
65template<typename Component>
67 scenes::node node;
68}; // struct component_removed_event
69
70class scene {
71
72 // friend class node;
73
74 static constexpr auto csm_cascade_count = std::uint32_t{4u};
75
76 struct csm_data {
77 std::array<math::matrix4x4, csm_cascade_count> light_spaces{};
78 math::vector4 cascade_splits{};
79 }; // struct csm_data
80
81public:
82
84
85 // template<typename... Get, typename... Exclude>
86 // using query_result = ecs::basic_view
87
88 // template<typename Type, typename... Other>
89 // using const_query_result = registry_type::const_view_type<Type, Other...>;
90
91 template<typename... Exclude>
92 inline static constexpr auto query_filter = ecs::exclude<Exclude...>;
93
94 scene(const std::filesystem::path& path);
95
96 virtual ~scene() = default;
97
98 auto create_child_node(const scenes::node parent, const std::string& tag = "Node", const scenes::transform& transform = scenes::transform{}) -> scenes::node;
99
100 auto create_node(const std::string& tag = "Node", const scenes::transform& transform = scenes::transform{}) -> scenes::node;
101
102 auto destroy_node(const scenes::node node) -> void;
103
104 auto camera() -> scenes::node {
105 return _camera;
106 }
107
108 auto set_active_camera(const scenes::node camera) -> void {
109 _camera = camera;
110 }
111
112 auto world_transform(const scenes::node node) -> math::matrix4x4;
113
114 auto world_normal(const scenes::node node) -> math::matrix4x4;
115
116 auto parent_transform(const scenes::node node) -> math::matrix4x4;
117
118 auto world_position(const scenes::node node) -> math::vector3;
119
120 auto world_rotation(const scenes::node node) -> math::quaternion;
121
122 auto world_scale(const scenes::node node) -> math::vector3;
123
124 auto make_child_of(const scenes::node node, const scenes::node parent) -> void;
125
126 auto is_valid(const scenes::node node) const -> bool {
127 return _registry.is_valid(node);
128 }
129
130 template<typename Type, typename... Other, typename... Exclude>
131 auto query(ecs::exclude_t<Exclude...> = ecs::exclude_t{}) -> decltype(auto) {
132 return _registry.view<Type, Other...>(ecs::exclude<Exclude...>);
133 }
134
135 template<typename Type, typename... Other, typename... Exclude>
136 auto query(ecs::exclude_t<Exclude...> = ecs::exclude_t{}) const -> decltype(auto) {
137 return _registry.view<Type, Other...>(ecs::exclude<Exclude...>);
138 }
139
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)...);
143 }
144
145 template<typename Component>
146 auto has_component(const scenes::node node) const -> bool {
147 return _registry.all_of<Component>(node);
148 }
149
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)...);
153 }
154
155 template<typename Component>
156 auto get_component(const scenes::node node) const -> const Component& {
157 return _registry.get<Component>(node);
158 }
159
160 template<typename Component>
161 auto get_component(const scenes::node node) -> Component& {
162 return _registry.get<Component>(node);
163 }
164
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)...);
168 }
169
170 auto light() -> directional_light& {
171 return _light;
172 }
173
174 auto root() -> scenes::node {
175 return _root;
176 }
177
178 auto light_space() -> math::matrix4x4 {
179 const auto& camera = get_component<scenes::camera>(_camera);
180
181 const auto camera_view = math::matrix4x4::inverted(world_transform(_camera));
182 const auto camera_projection = camera.projection(0.1f, 100.0f);
183
184 const auto inverse_view_projection = math::matrix4x4::inverted(camera_projection * camera_view);
185
186 static constexpr auto frustum_corners_clip = std::array<math::vector4, 8u>{
187 math::vector4{-1.0f, -1.0f, 0.0f, 1.0f},
188 math::vector4{ 1.0f, -1.0f, 0.0f, 1.0f},
189 math::vector4{ 1.0f, 1.0f, 0.0f, 1.0f},
190 math::vector4{-1.0f, 1.0f, 0.0f, 1.0f},
191 math::vector4{-1.0f, -1.0f, 1.0f, 1.0f},
192 math::vector4{ 1.0f, -1.0f, 1.0f, 1.0f},
193 math::vector4{ 1.0f, 1.0f, 1.0f, 1.0f},
194 math::vector4{-1.0f, 1.0f, 1.0f, 1.0f}
195 };
196
197 auto frustum_corners_world = std::array<math::vector3, 8u>{};
198
199 for (auto i = 0u; i < 8u; i++) {
200 auto corner_world = inverse_view_projection * frustum_corners_clip[i];
201
202 corner_world /= corner_world.w();
203 frustum_corners_world[i] = math::vector3{corner_world};
204 }
205
206 auto center = math::vector3::zero;
207
208 for (const auto& corner : frustum_corners_world) {
209 center += corner;
210 }
211
212 center /= 8.0f;
213
214 const auto light_dir = math::vector3::normalized(_light.direction());
215
216 // Stable up vector when light is near-vertical
217 auto up = math::vector3::up;
218
219 if (std::abs(math::vector3::dot(light_dir, up)) > 0.99f) {
220 up = math::vector3{1.0f, 0.0f, 0.0f};
221 }
222
223 // First pass: place light at center to measure Z extent
224 const auto light_view_initial = math::matrix4x4::look_at(center, center + light_dir, up);
225
226 auto min_z = std::numeric_limits<std::float_t>::max();
227 auto max_z = std::numeric_limits<std::float_t>::lowest();
228
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());
233 }
234
235 // Pull the light back far enough to capture all casters
236 static constexpr auto z_caster_padding = 50.0f;
237 const auto pull_back = std::abs(min_z) + z_caster_padding;
238
239 const auto light_position = center - light_dir * pull_back;
240
241 // Second pass: real light view from computed position
242 const auto light_view = math::matrix4x4::look_at(light_position, center, up);
243
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()};
246
247 for (const auto& corner : frustum_corners_world) {
248 const auto corner_light_space = light_view * math::vector4{corner, 1.0f};
249 const auto p = math::vector3{corner_light_space};
250
251 min_bounds = math::vector3::min(min_bounds, p);
252 max_bounds = math::vector3::max(max_bounds, p);
253 }
254
255 // Z bounds with padding for off-screen shadow casters
256 const auto z_range = max_bounds.z() - min_bounds.z();
257 const auto z_padding = std::max(z_range * 2.0f, 50.0f);
258
259 auto near_plane = -(max_bounds.z() + z_padding);
260 auto far_plane = -(min_bounds.z() - z_padding);
261
262 if (near_plane > far_plane) {
263 std::swap(near_plane, far_plane);
264 }
265
266 // XY padding for edge stability
267 static constexpr auto xy_padding = 10.0f;
268
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
273 );
274
275 return light_projection * light_view;
276 }
277
278
279 auto find_node(const scenes::id& id) -> scenes::node {
280 if (auto entry = _nodes.find(id); entry != _nodes.end()) {
281 return entry->second;
282 }
283
284 return scenes::node::null;
285 }
286
287 auto save(const std::filesystem::path& path)-> void;
288
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>();
292
293 const auto id = graphics_module.add_resource<graphics::image2d>(path, std::forward<Args>(args)...);
294
295 _image_ids.emplace(name, id);
296 _image_metadata.emplace(id, assets::asset_metadata{path, name.str(), "image", "disk"});
297 }
298
299 auto has_image(const utility::hashed_string& name) const -> bool {
300 return _image_ids.find(name) != _image_ids.end();
301 }
302
303 auto get_image(const utility::hashed_string& name) -> graphics::image2d_handle {
304 if (auto entry = _image_ids.find(name); entry != _image_ids.end()) {
305 return entry->second;
306 }
307
308 throw utility::runtime_error{"Could not find image '{}", name.str()};
309 }
310
311 auto image_metadata(const graphics::image2d_handle& handle) const -> const assets::asset_metadata& {
312 return _image_metadata.at(handle);
313 }
314
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>();
318
319 const auto id = graphics_module.add_resource<graphics::cube_image>(path, std::forward<Args>(args)...);
320
321 _cube_image_ids.emplace(name, id);
322 _cube_image_metadata.emplace(id, assets::asset_metadata{path, name.str(), "cube_image", "disk"});
323 }
324
325 auto get_cube_image(const utility::hashed_string& name) -> graphics::cube_image2d_handle {
326 return _cube_image_ids.at(name);
327 }
328
329 auto cube_image_metadata(const graphics::cube_image2d_handle& handle) const -> const assets::asset_metadata& {
330 return _cube_image_metadata.at(handle);
331 }
332
333 template<typename Mesh, typename... Args>
334 auto add_mesh(const utility::hashed_string& name, Args&&... args) -> void {
335 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
336
337 const auto id = assets_module.add_asset<Mesh>(std::forward<Args>(args)...);
338
339 _mesh_ids.emplace(name, id);
340 _mesh_metadata.emplace(id, assets::asset_metadata{"", name.str(), "mesh", "generated"});
341 }
342
343 template<typename Mesh, typename Path, typename... Args>
344 requires (std::is_constructible_v<std::filesystem::path, Path>)
345 auto add_mesh(const utility::hashed_string& name, const Path& path, Args&&... args) -> void {
346 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
347
348
349 const auto id = assets_module.add_asset<Mesh>(path, std::forward<Args>(args)...);
350
351 _mesh_ids.emplace(name, id);
352 _mesh_metadata.emplace(id, assets::asset_metadata{path, name.str(), "mesh", "disk"});
353 }
354
355 auto get_mesh(const utility::hashed_string& name) -> math::uuid {
356 return _mesh_ids.at(name);
357 }
358
359 auto mesh_metadata(const math::uuid& handle) const -> const assets::asset_metadata& {
360 return _mesh_metadata.at(handle);
361 }
362
363 template<typename Mesh, typename... Args>
364 auto add_animation(const utility::hashed_string& name, Args&&... args) -> void {
365 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
366
367 _animation_ids.emplace(name, assets_module.add_asset<Mesh>(std::forward<Args>(args)...));
368 }
369
370 auto get_animation(const utility::hashed_string& name) -> math::uuid {
371 return _animation_ids.at(name);
372 }
373
374 template<typename Material, typename... Args>
375 auto add_material(const utility::hashed_string& name, Args&&... args) -> Material& {
376 auto& assets_module = sbx::core::engine::get_module<sbx::assets::assets_module>();
377
378 const auto id = assets_module.add_asset<Material>(std::forward<Args>(args)...);
379
380 _materials_ids.emplace(name, id);
381 _material_metadata.emplace(id, assets::asset_metadata{"", name.str(), "material", "dynamic"});
382
383 return assets_module.get_asset<Material>(id);
384 }
385
386 auto has_material(const utility::hashed_string& name) const -> bool {
387 return _materials_ids.find(name) != _materials_ids.end();
388 }
389
390 auto get_material(const utility::hashed_string& name) -> math::uuid {
391 return _materials_ids.at(name);
392 }
393
394 auto material_metadata(const math::uuid& handle) const -> const assets::asset_metadata& {
395 return _material_metadata.at(handle);
396 }
397
398 auto uniform_handler() -> graphics::uniform_handler& {
399 return _uniform_handler;
400 }
401
402 auto update_uniform_handler() -> void {
403 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
404
405 auto& camera = get_component<scenes::camera>(_camera);
406
407 const auto& projection = camera.projection();
408
409 _uniform_handler.push("projection", projection);
410 _uniform_handler.push("inverse_projection", math::matrix4x4::inverted(projection));
411
412 auto inverse_view = world_transform(_camera);
413
414 const auto view = math::matrix4x4::inverted(inverse_view);
415
416 inverse_view[3] = math::vector4{0.0f, 0.0f, 0.0f, 1.0f};
417
418 _uniform_handler.push("view", view);
419 _uniform_handler.push("inverse_view", inverse_view);
420
421 _uniform_handler.push("viewport", graphics_module.viewport());
422
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());
427
428 const auto& camera_transform = get_component<scenes::transform>(_camera);
429
430 const auto camera_rotation = math::matrix_cast<math::matrix4x4>(world_rotation(_camera));
431 const auto camera_right = math::vector3{camera_rotation[0]};
432 const auto camera_up = math::vector3{camera_rotation[1]};
433
434 _uniform_handler.push("camera_right", camera_right);
435 _uniform_handler.push("camera_up", camera_up);
436
437 const auto csm = _build_csm();
438
439 _uniform_handler.push("light_spaces", csm.light_spaces);
440 _uniform_handler.push("cascade_splits", csm.cascade_splits);
441
442 _uniform_handler.push("light_direction", sbx::math::vector3::normalized(_light.direction()));
443 _uniform_handler.push("light_color", _light.color());
444
445 _uniform_handler.push("time", core::engine::time().value());
446 }
447
448 auto screen_point_to_ray(const math::vector2& position) -> math::ray {
449 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
450 const auto& viewport = graphics_module.viewport();
451
452 const auto& camera = get_component<scenes::camera>(_camera);
453
454 const auto world = world_transform(_camera);
455 const auto view = math::matrix4x4::inverted(world);
456 const auto projection = camera.projection();
457
458 const auto inverse_view_projection = math::matrix4x4::inverted(projection * view);
459
460 const auto px = position.x() + 0.5f;
461 const auto py = position.y() + 0.5f;
462
463 const auto x = (2.0f * px) / viewport.x() - 1.0f;
464 const auto y = (2.0f * py) / viewport.y() - 1.0f;
465
466 const auto near_clip = math::vector4{x, y, 0.0f, 1.0f};
467 const auto far_clip = math::vector4{x, y, 1.0f, 1.0f};
468
469 auto near_world = inverse_view_projection * near_clip;
470 near_world /= near_world.w();
471
472 auto far_world = inverse_view_projection * far_clip;
473 far_world /= far_world.w();
474
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);
477
478 return math::ray{origin, direction};
479 }
480
481 auto node_count() const -> std::size_t {
482 return _nodes.size();
483 }
484
485private:
486
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;
489 }
490
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>{};
493
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);
496
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;
499
500 splits[i] = _lerp_float(uni_split, log_split, lambda);
501 }
502
503 return splits;
504 }
505
506 static auto _build_light_space_for_slice(const scenes::camera& camera, const math::matrix4x4& camera_world, const math::vector3& light_direction, const std::float_t slice_near, const std::float_t slice_far, const std::uint32_t shadow_resolution) -> math::matrix4x4 {
507 const auto camera_view = math::matrix4x4::inverted(camera_world);
508 const auto camera_projection = camera.projection(slice_near, slice_far);
509
510 const auto inv_view_projection = math::matrix4x4::inverted(camera_projection * camera_view);
511
512 static constexpr auto frustum_corners_clip = std::array<math::vector4, 8u>{
513 math::vector4{-1.0f, -1.0f, 0.0f, 1.0f},
514 math::vector4{ 1.0f, -1.0f, 0.0f, 1.0f},
515 math::vector4{ 1.0f, 1.0f, 0.0f, 1.0f},
516 math::vector4{-1.0f, 1.0f, 0.0f, 1.0f},
517 math::vector4{-1.0f, -1.0f, 1.0f, 1.0f},
518 math::vector4{ 1.0f, -1.0f, 1.0f, 1.0f},
519 math::vector4{ 1.0f, 1.0f, 1.0f, 1.0f},
520 math::vector4{-1.0f, 1.0f, 1.0f, 1.0f}
521 };
522
523 auto corners_world = std::array<math::vector3, 8u>{};
524 auto center_world = math::vector3::zero;
525
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();
529
530 corners_world[i] = math::vector3{corner_world};
531 center_world += corners_world[i];
532 }
533
534 center_world /= 8.0f;
535
536 const auto light_dir = math::vector3::normalized(light_direction);
537
538 auto up = math::vector3::up;
539
540 if (std::abs(math::vector3::dot(light_dir, up)) > 0.99f) {
541 up = math::vector3{1.0f, 0.0f, 0.0f};
542 }
543
544 // First pass: measure Z extent from the center
545 const auto light_view_initial = math::matrix4x4::look_at(center_world, center_world + light_dir, up);
546
547 auto min_z = std::numeric_limits<std::float_t>::max();
548 auto max_z = std::numeric_limits<std::float_t>::lowest();
549
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());
554 }
555
556 // Pull back light to capture all shadow casters
557 static constexpr auto z_caster_padding = 50.0f;
558 const auto pull_back = std::abs(min_z) + z_caster_padding;
559
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);
562
563 // Find XY and Z bounds in final light space
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()};
566
567 for (const auto& corner : corners_world) {
568 const auto p4 = light_view * math::vector4{corner, 1.0f};
569 const auto p = math::vector3{p4};
570
571 min_bounds = math::vector3::min(min_bounds, p);
572 max_bounds = math::vector3::max(max_bounds, p);
573 }
574
575 // Square the XY extent and snap to texel grid to prevent shimmer
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;
580
581 const auto texel_size = extent / static_cast<std::float_t>(shadow_resolution);
582
583 const auto center_ls = (min_bounds + max_bounds) * 0.5f;
584
585 auto min_x = center_ls.x() - extent * 0.5f;
586 auto min_y = center_ls.y() - extent * 0.5f;
587
588 min_x = std::floor(min_x / texel_size) * texel_size;
589 min_y = std::floor(min_y / texel_size) * texel_size;
590
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);
593
594 // Z bounds with padding
595 const auto z_range = max_bounds.z() - min_bounds.z();
596 const auto z_padding = std::max(z_range * 2.0f, 50.0f);
597
598 auto near_plane = -(max_bounds.z() + z_padding);
599 auto far_plane = -(min_bounds.z() - z_padding);
600
601 if (near_plane > far_plane) {
602 std::swap(near_plane, far_plane);
603 }
604
605 const auto light_projection = math::matrix4x4::orthographic(
606 min_x, max_x,
607 min_y, max_y,
608 near_plane, far_plane
609 );
610
611 return light_projection * light_view;
612 }
613
614 auto _build_csm() -> csm_data {
615 const auto& camera = get_component<scenes::camera>(_camera);
616 const auto camera_world = world_transform(_camera);
617
618 const auto camera_near = camera.near_plane();
619 const auto camera_far = camera.far_plane();
620
621 static constexpr auto lambda = 0.65f;
622 static constexpr auto shadow_resolution = std::uint32_t{2048u};
623
624 const auto splits = _compute_csm_splits(camera_near, camera_far, lambda);
625
626 auto result = csm_data{};
627
628 auto slice_near = camera_near;
629
630 for (auto i = 0u; i < csm_cascade_count; ++i) {
631 const auto slice_far = splits[i];
632
633 result.light_spaces[i] = _build_light_space_for_slice(camera, camera_world, _light.direction(), slice_near, slice_far, shadow_resolution);
634
635 slice_near = slice_far;
636 }
637
638 result.cascade_splits = math::vector4{splits[0], splits[1], splits[2], camera_far};
639
640 return result;
641 }
642
643 auto _save_assets(YAML::Emitter& emitter) -> void;
644
645 auto _save_meshes(YAML::Emitter& emitter) -> void;
646
647 auto _save_textures(YAML::Emitter& emitter) -> void;
648
649 auto _save_node(YAML::Emitter& emitter, const scenes::node node) -> void;
650
651 auto _save_components(YAML::Emitter& emitter, const scenes::node node) -> void;
652
653 auto _load_assets(const YAML::Node& assets) -> void;
654
655 auto _load_nodes(const YAML::Node& nodes) -> void;
656
657 auto _ensure_world(const scenes::node node) -> const scenes::global_transform&;
658
659 std::unordered_map<math::uuid, scenes::node> _nodes;
660
661 registry_type _registry;
662 scenes::node _root;
663 scenes::node _camera;
664
665 std::string _name;
666
667 directional_light _light;
668
669 graphics::uniform_handler _uniform_handler;
670
671
672
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;
678
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;
683
684}; // class scene
685
687 std::string name;
688 std::function<void(YAML::Emitter&, scene& scene, const node)> save;
689 std::function<void(const YAML::Node&, scene& scene, const node)> load;
690}; // struct component_io
691
693
694public:
695
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 {
698 const auto id = ecs::type_id<Type>::value();
699
700 _by_name[name] = id;
701
702 _by_id[id] = component_io{
703 .name = name,
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);
706
707 std::invoke(s, yaml, scene, component);
708 },
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));
711 }
712 };
713 }
714
715 auto get(const std::uint32_t id) -> component_io& {
716 return _by_id.at(id);
717 }
718
719 auto has(const std::uint32_t id) -> bool {
720 return _by_id.contains(id);
721 }
722
723 auto get(const std::string& name) -> component_io& {
724 return _by_id.at(_by_name.at(name));
725 }
726
727private:
728
729 std::unordered_map<std::uint32_t, component_io> _by_id;
730 std::unordered_map<std::string, std::uint32_t> _by_name;
731
732}; // class component_io_registry
733
734} // namespace sbx::scenes
735
736#endif // LIBSBX_SCENES_SCENE_HPP_
Definition: tests.cpp:6
Definition: cube_image.hpp:21
Definition: image2d.hpp:50
Definition: resource_storage.hpp:18
Definition: uniform_handler.hpp:16
Definition: matrix4x4.hpp:26
Definition: quaternion.hpp:25
Definition: uuid.hpp:160
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: tag.hpp:12
Definition: camera.hpp:96
Definition: scene.hpp:692
Definition: directional_light.hpp:10
Definition: id.hpp:9
Definition: scene.hpp:70
Definition: transform.hpp:16
Definition: hashed_string.hpp:17
Matrix casting and decomposition utilities.
Ray type for geometric queries.
Definition: assets_module.hpp:90
Definition: view.hpp:310
Definition: scene.hpp:686
Definition: scene.hpp:66
Definition: global_transform.hpp:9
Definition: scene.hpp:61
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