From db8db8f9d7d5c38ff0d7ccb8851d6c77646cfd54 Mon Sep 17 00:00:00 2001 From: ZY4N Date: Sun, 22 Dec 2024 16:58:40 +0100 Subject: [PATCH] fixes --- .clang-tidy | 148 ++ .gitignore | 1 + .idea/.gitignore | 8 + .idea/codeStyles/Project.xml | 158 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/misc.xml | 13 + .idea/modules.xml | 8 + .idea/point_cloud_viewer.iml | 2 + .idea/vcs.xml | 8 + CMakeLists.txt | 196 ++ data/fonts/JetBrainsMono_Medium.ttf | Bin 0 -> 204140 bytes .../assets/components/material_components.hpp | 59 + .../components/mesh_vertex_components.hpp | 39 + .../point_cloud_vertex_components.hpp | 36 + .../assets/components/texture_components.hpp | 28 + include/assets/data/surface_properties.hpp | 11 + include/assets/data_loaders/glsl_loader.hpp | 30 + include/assets/data_loaders/kitti_loader.hpp | 53 + .../assets/data_loaders/kitti_pose_loader.hpp | 37 + include/assets/data_loaders/mtl_loader.hpp | 56 + include/assets/data_loaders/obj_loader.hpp | 42 + include/assets/data_loaders/stl_loader.hpp | 30 + .../data_loaders/threedtk_pose_loader.hpp | 40 + include/assets/data_loaders/uos_loader.hpp | 9 + .../assets/data_loaders/uos_normal_loader.hpp | 8 + .../assets/data_loaders/uos_rgb_loader.hpp | 8 + include/assets/data_loaders/uosr_loader.hpp | 8 + .../dynamic_material_library_loader.hpp | 36 + .../dynamic_material_loader.hpp | 34 + .../dynamic_mesh_loader.hpp | 35 + .../dynamic_point_cloud_loader.hpp | 46 + .../dynamic_texture_loader.hpp | 60 + .../generic/base_dynamic_loader.hpp | 39 + .../dynamic_material_library_store.hpp | 6 + .../dynamic_material_store.hpp | 37 + .../dynamic_mesh_store.hpp | 37 + .../dynamic_point_cloud_store.hpp | 36 + .../dynamic_pose_store.hpp | 6 + .../dynamic_shader_store.hpp | 9 + .../dynamic_texture_store.hpp | 6 + .../dynamic_vertex_store.hpp | 37 + .../generic_dynamic_component_array_store.hpp | 149 ++ .../generic_dynamic_component_store.hpp | 147 ++ ..._dynamic_indexed_component_array_store.hpp | 157 ++ .../generic/generic_dynamic_store.hpp | 40 + .../dynamic_material_buffer.hpp | 48 + .../dynamic_material_library_buffer.hpp | 6 + .../dynamic_mesh_buffer.hpp | 46 + .../dynamic_point_cloud_buffer.hpp | 30 + .../dynamic_pose_buffer.hpp | 5 + .../dynamic_shader_buffer.hpp | 10 + .../dynamic_texture_buffer.hpp | 77 + .../dynamic_vertex_buffer.hpp | 15 + include/assets/prefetch_lookup.hpp | 20 + .../material_library_prefetch_lookup.hpp | 7 + .../material_prefetch_lookup.hpp | 7 + .../prefetch_lookups/mesh_prefetch_lookup.hpp | 7 + .../point_cloud_prefetch_lookup.hpp | 7 + .../prefetch_lookups/pose_prefetch_lookup.hpp | 58 + .../shader_prefetch_lookup.hpp | 7 + .../texture_prefetch_lookup.hpp | 7 + include/assets/prefetch_queue.hpp | 28 + include/opengl/data/material_data.hpp | 58 + include/opengl/data/mesh_data.hpp | 68 + include/opengl/data/point_cloud_data.hpp | 55 + include/opengl/data/shader_data.hpp | 43 + include/opengl/data/texture_data.hpp | 45 + .../data_uploaders/texture_data_uploader.hpp | 84 + include/opengl/handles/shader_handle.hpp | 13 + include/opengl/shader_program_lookup.hpp | 43 + .../batch_renderers/mesh_batch_renderer.hpp | 65 + .../point_cloud_batch_renderer.hpp | 59 + include/rendering/batches/mesh_batch.hpp | 55 + .../rendering/batches/point_cloud_batch.hpp | 46 + include/rendering/modes/mesh_modes.hpp | 13 + include/rendering/modes/point_cloud_modes.hpp | 11 + .../requirements/mesh_requirements.hpp | 127 ++ .../requirements/point_cloud_requirements.hpp | 122 ++ .../shader_program_lookups/mesh_lookup.hpp | 27 + .../point_cloud_lookup.hpp | 25 + include/scene/camera.hpp | 21 + include/scene/flying_camera.hpp | 30 + include/scene/lighting_setup.hpp | 10 + .../attributes/mesh_attributes.hpp | 83 + .../attributes/point_cloud_attributes.hpp | 84 + .../capabilities/mesh_capabilities.hpp | 85 + .../capabilities/point_cloud_capabilities.hpp | 77 + .../shader_program/uniforms/mesh_uniforms.hpp | 117 ++ .../uniforms/point_cloud_uniforms.hpp | 97 + include/util/arx.hpp | 285 +++ include/util/binary_ifstream.hpp | 207 ++ include/util/enum_operators.hpp | 57 + include/util/extra_arx_parsers.hpp | 28 + include/util/for_each.hpp | 49 + include/util/function.hpp | 63 + include/util/id_type.hpp | 25 + include/util/image.hpp | 327 ++++ include/util/line_parser.hpp | 87 + include/util/logger.hpp | 1742 +++++++++++++++++ include/util/result.hpp | 139 ++ include/util/specialised_lambda.hpp | 10 + include/util/string_indexer.hpp | 126 ++ include/util/string_list.hpp | 259 +++ include/util/string_literal.hpp | 734 +++++++ include/util/string_lookup.hpp | 35 + include/util/uix.hpp | 90 + include/util/unroll_bool_template.hpp | 19 + include/viewer/asset_loader.hpp | 195 ++ include/viewer/asset_types.hpp | 9 + .../viewer/dynamic_shader_program_loading.hpp | 24 + include/viewer/instance.hpp | 118 ++ include/viewer/settings.hpp | 18 + main.cpp | 431 ++++ .../generic/generic_3dtk_loader.ipp | 444 +++++ source/assets/data_loaders/glsl_loader.cpp | 36 + source/assets/data_loaders/kitti_loader.cpp | 302 +++ .../assets/data_loaders/kitti_pose_loader.cpp | 163 ++ source/assets/data_loaders/mtl_loader.cpp | 398 ++++ source/assets/data_loaders/obj_loader.cpp | 450 +++++ source/assets/data_loaders/stl_loader.cpp | 253 +++ .../data_loaders/threedtk_pose_loader.cpp | 136 ++ .../dynamic_mesh_loader.cpp | 41 + .../dynamic_point_cloud_loader.cpp | 40 + .../dynamic_texture_loader.cpp | 122 ++ .../generic/base_dynamic_loader.ipp | 92 + .../dynamic_material_store.cpp | 26 + .../dynamic_mesh_store.cpp | 31 + .../dynamic_point_cloud_store.cpp | 27 + .../generic_dynamic_component_array_store.ipp | 387 ++++ .../generic_dynamic_component_store.ipp | 373 ++++ ..._dynamic_indexed_component_array_store.ipp | 418 ++++ .../generic/generic_dynamic_store.ipp | 61 + .../dynamic_material_buffer.ipp | 88 + .../dynamic_mesh_buffer.ipp | 73 + .../dynamic_model_buffer.ipp | 148 ++ .../dynamic_point_cloud_buffer.ipp | 44 + .../dynamic_texture_buffer.ipp | 151 ++ .../prefetch_lookups/pose_prefetch_lookup.cpp | 139 ++ source/geometry/normal_estimation.cpp | 82 + source/opengl/data/material_data.ipp | 129 ++ source/opengl/data/mesh_data.cpp | 119 ++ source/opengl/data/mesh_data.ipp | 90 + source/opengl/data/point_cloud_data.cpp | 99 + source/opengl/data/point_cloud_data.ipp | 66 + source/opengl/data/shader_data.cpp | 46 + source/opengl/data/shader_data.ipp | 47 + source/opengl/data/shader_program_data.cpp | 90 + source/opengl/data/shader_program_data.ipp | 45 + source/opengl/data/texture_data.ipp | 79 + .../opengl/handles/shader_program_handle.cpp | 152 ++ source/opengl/shader_program_lookup.cpp | 195 ++ .../batch_renderers/mesh_batch_renderer.cpp | 341 ++++ .../point_cloud_batch_renderer.cpp | 243 +++ source/rendering/batches/mesh_batch.ipp | 147 ++ .../rendering/batches/point_cloud_batch.ipp | 69 + .../shader_program_lookups/mesh_lookup.cpp | 58 + .../point_cloud_lookup.cpp | 47 + source/scene/flying_camera.cpp | 117 ++ source/viewer/asset_loader.cpp | 636 ++++++ .../viewer/dynamic_shader_program_loading.cpp | 275 +++ source/viewer/instance.cpp | 504 +++++ 161 files changed, 17102 insertions(+) create mode 100755 .clang-tidy create mode 100755 .gitignore create mode 100755 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100755 .idea/codeStyles/codeStyleConfig.xml create mode 100755 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/point_cloud_viewer.iml create mode 100755 .idea/vcs.xml create mode 100755 CMakeLists.txt create mode 100755 data/fonts/JetBrainsMono_Medium.ttf create mode 100644 include/assets/components/material_components.hpp create mode 100755 include/assets/components/mesh_vertex_components.hpp create mode 100755 include/assets/components/point_cloud_vertex_components.hpp create mode 100644 include/assets/components/texture_components.hpp create mode 100644 include/assets/data/surface_properties.hpp create mode 100644 include/assets/data_loaders/glsl_loader.hpp create mode 100644 include/assets/data_loaders/kitti_loader.hpp create mode 100644 include/assets/data_loaders/kitti_pose_loader.hpp create mode 100644 include/assets/data_loaders/mtl_loader.hpp create mode 100755 include/assets/data_loaders/obj_loader.hpp create mode 100644 include/assets/data_loaders/stl_loader.hpp create mode 100644 include/assets/data_loaders/threedtk_pose_loader.hpp create mode 100644 include/assets/data_loaders/uos_loader.hpp create mode 100644 include/assets/data_loaders/uos_normal_loader.hpp create mode 100644 include/assets/data_loaders/uos_rgb_loader.hpp create mode 100644 include/assets/data_loaders/uosr_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/dynamic_material_library_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/dynamic_material_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/dynamic_mesh_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/dynamic_texture_loader.hpp create mode 100644 include/assets/dynamic_data_loaders/generic/base_dynamic_loader.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_material_library_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_material_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_mesh_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_point_cloud_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_pose_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_shader_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_texture_store.hpp create mode 100644 include/assets/dynamic_data_stores/dynamic_vertex_store.hpp create mode 100644 include/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp create mode 100644 include/assets/dynamic_data_stores/generic/generic_dynamic_component_store.hpp create mode 100644 include/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp create mode 100644 include/assets/dynamic_data_stores/generic/generic_dynamic_store.hpp create mode 100755 include/assets/dynamic_read_buffers/dynamic_material_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_pose_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_shader_buffer.hpp create mode 100755 include/assets/dynamic_read_buffers/dynamic_texture_buffer.hpp create mode 100644 include/assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp create mode 100644 include/assets/prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/material_library_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/material_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/mesh_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/pose_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/shader_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_lookups/texture_prefetch_lookup.hpp create mode 100644 include/assets/prefetch_queue.hpp create mode 100644 include/opengl/data/material_data.hpp create mode 100755 include/opengl/data/mesh_data.hpp create mode 100644 include/opengl/data/point_cloud_data.hpp create mode 100644 include/opengl/data/shader_data.hpp create mode 100644 include/opengl/data/texture_data.hpp create mode 100644 include/opengl/data_uploaders/texture_data_uploader.hpp create mode 100644 include/opengl/handles/shader_handle.hpp create mode 100644 include/opengl/shader_program_lookup.hpp create mode 100644 include/rendering/batch_renderers/mesh_batch_renderer.hpp create mode 100644 include/rendering/batch_renderers/point_cloud_batch_renderer.hpp create mode 100644 include/rendering/batches/mesh_batch.hpp create mode 100644 include/rendering/batches/point_cloud_batch.hpp create mode 100644 include/rendering/modes/mesh_modes.hpp create mode 100644 include/rendering/modes/point_cloud_modes.hpp create mode 100644 include/rendering/requirements/mesh_requirements.hpp create mode 100644 include/rendering/requirements/point_cloud_requirements.hpp create mode 100644 include/rendering/shader_program_lookups/mesh_lookup.hpp create mode 100644 include/rendering/shader_program_lookups/point_cloud_lookup.hpp create mode 100755 include/scene/camera.hpp create mode 100755 include/scene/flying_camera.hpp create mode 100644 include/scene/lighting_setup.hpp create mode 100644 include/shader_program/attributes/mesh_attributes.hpp create mode 100644 include/shader_program/attributes/point_cloud_attributes.hpp create mode 100644 include/shader_program/capabilities/mesh_capabilities.hpp create mode 100644 include/shader_program/capabilities/point_cloud_capabilities.hpp create mode 100644 include/shader_program/uniforms/mesh_uniforms.hpp create mode 100644 include/shader_program/uniforms/point_cloud_uniforms.hpp create mode 100755 include/util/arx.hpp create mode 100644 include/util/binary_ifstream.hpp create mode 100644 include/util/enum_operators.hpp create mode 100755 include/util/extra_arx_parsers.hpp create mode 100755 include/util/for_each.hpp create mode 100755 include/util/function.hpp create mode 100644 include/util/id_type.hpp create mode 100755 include/util/image.hpp create mode 100644 include/util/line_parser.hpp create mode 100755 include/util/logger.hpp create mode 100644 include/util/result.hpp create mode 100644 include/util/specialised_lambda.hpp create mode 100755 include/util/string_indexer.hpp create mode 100644 include/util/string_list.hpp create mode 100644 include/util/string_literal.hpp create mode 100644 include/util/string_lookup.hpp create mode 100755 include/util/uix.hpp create mode 100644 include/util/unroll_bool_template.hpp create mode 100644 include/viewer/asset_loader.hpp create mode 100644 include/viewer/asset_types.hpp create mode 100644 include/viewer/dynamic_shader_program_loading.hpp create mode 100644 include/viewer/instance.hpp create mode 100644 include/viewer/settings.hpp create mode 100755 main.cpp create mode 100644 source/assets/data_loaders/generic/generic_3dtk_loader.ipp create mode 100644 source/assets/data_loaders/glsl_loader.cpp create mode 100644 source/assets/data_loaders/kitti_loader.cpp create mode 100644 source/assets/data_loaders/kitti_pose_loader.cpp create mode 100644 source/assets/data_loaders/mtl_loader.cpp create mode 100755 source/assets/data_loaders/obj_loader.cpp create mode 100644 source/assets/data_loaders/stl_loader.cpp create mode 100644 source/assets/data_loaders/threedtk_pose_loader.cpp create mode 100644 source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp create mode 100644 source/assets/dynamic_data_loaders/dynamic_point_cloud_loader.cpp create mode 100644 source/assets/dynamic_data_loaders/dynamic_texture_loader.cpp create mode 100644 source/assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp create mode 100644 source/assets/dynamic_data_stores/dynamic_material_store.cpp create mode 100644 source/assets/dynamic_data_stores/dynamic_mesh_store.cpp create mode 100644 source/assets/dynamic_data_stores/dynamic_point_cloud_store.cpp create mode 100644 source/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp create mode 100644 source/assets/dynamic_data_stores/generic/generic_dynamic_component_store.ipp create mode 100644 source/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.ipp create mode 100644 source/assets/dynamic_data_stores/generic/generic_dynamic_store.ipp create mode 100644 source/assets/dynamic_read_buffers/dynamic_material_buffer.ipp create mode 100644 source/assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp create mode 100644 source/assets/dynamic_read_buffers/dynamic_model_buffer.ipp create mode 100644 source/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp create mode 100644 source/assets/dynamic_read_buffers/dynamic_texture_buffer.ipp create mode 100644 source/assets/prefetch_lookups/pose_prefetch_lookup.cpp create mode 100644 source/geometry/normal_estimation.cpp create mode 100644 source/opengl/data/material_data.ipp create mode 100644 source/opengl/data/mesh_data.cpp create mode 100644 source/opengl/data/mesh_data.ipp create mode 100644 source/opengl/data/point_cloud_data.cpp create mode 100644 source/opengl/data/point_cloud_data.ipp create mode 100755 source/opengl/data/shader_data.cpp create mode 100644 source/opengl/data/shader_data.ipp create mode 100755 source/opengl/data/shader_program_data.cpp create mode 100755 source/opengl/data/shader_program_data.ipp create mode 100644 source/opengl/data/texture_data.ipp create mode 100644 source/opengl/handles/shader_program_handle.cpp create mode 100644 source/opengl/shader_program_lookup.cpp create mode 100644 source/rendering/batch_renderers/mesh_batch_renderer.cpp create mode 100644 source/rendering/batch_renderers/point_cloud_batch_renderer.cpp create mode 100644 source/rendering/batches/mesh_batch.ipp create mode 100644 source/rendering/batches/point_cloud_batch.ipp create mode 100644 source/rendering/shader_program_lookups/mesh_lookup.cpp create mode 100644 source/rendering/shader_program_lookups/point_cloud_lookup.cpp create mode 100755 source/scene/flying_camera.cpp create mode 100644 source/viewer/asset_loader.cpp create mode 100644 source/viewer/dynamic_shader_program_loading.cpp create mode 100644 source/viewer/instance.cpp diff --git a/.clang-tidy b/.clang-tidy new file mode 100755 index 0000000..de3935e --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,148 @@ +# Generated from CLion Inspection settings +--- +Checks: '-*, +bugprone-argument-comment, +bugprone-assert-side-effect, +bugprone-bad-signal-to-kill-thread, +bugprone-branch-clone, +bugprone-copy-constructor-init, +bugprone-dangling-handle, +bugprone-dynamic-static-initializers, +bugprone-fold-init-type, +bugprone-forward-declaration-namespace, +bugprone-forwarding-reference-overload, +bugprone-inaccurate-erase, +bugprone-incorrect-roundings, +bugprone-integer-division, +bugprone-lambda-function-name, +bugprone-macro-parentheses, +bugprone-macro-repeated-side-effects, +bugprone-misplaced-operator-in-strlen-in-alloc, +bugprone-misplaced-pointer-arithmetic-in-alloc, +bugprone-misplaced-widening-cast, +bugprone-move-forwarding-reference, +bugprone-multiple-statement-macro, +bugprone-no-escape, +bugprone-not-null-terminated-result, +bugprone-parent-virtual-call, +bugprone-posix-return, +bugprone-reserved-identifier, +bugprone-sizeof-container, +bugprone-sizeof-expression, +bugprone-spuriously-wake-up-functions, +bugprone-string-constructor, +bugprone-string-integer-assignment, +bugprone-string-literal-with-embedded-nul, +bugprone-suspicious-enum-usage, +bugprone-suspicious-include, +bugprone-suspicious-memset-usage, +bugprone-suspicious-missing-comma, +bugprone-suspicious-semicolon, +bugprone-suspicious-string-compare, +bugprone-suspicious-memory-comparison, +bugprone-suspicious-realloc-usage, +bugprone-swapped-arguments, +bugprone-terminating-continue, +bugprone-throw-keyword-missing, +bugprone-too-small-loop-variable, +bugprone-undefined-memory-manipulation, +bugprone-undelegated-constructor, +bugprone-unhandled-self-assignment, +bugprone-unused-raii, +bugprone-unused-return-value, +bugprone-use-after-move, +bugprone-virtual-near-miss, +cert-dcl21-cpp, +cert-dcl58-cpp, +cert-err34-c, +cert-err52-cpp, +cert-err60-cpp, +cert-flp30-c, +cert-msc50-cpp, +cert-msc51-cpp, +cert-str34-c, +cppcoreguidelines-interfaces-global-init, +cppcoreguidelines-narrowing-conversions, +cppcoreguidelines-pro-type-member-init, +cppcoreguidelines-pro-type-static-cast-downcast, +cppcoreguidelines-slicing, +google-default-arguments, +google-explicit-constructor, +google-runtime-operator, +hicpp-exception-baseclass, +hicpp-multiway-paths-covered, +misc-misplaced-const, +misc-new-delete-overloads, +misc-no-recursion, +misc-non-copyable-objects, +misc-throw-by-value-catch-by-reference, +misc-unconventional-assign-operator, +misc-uniqueptr-reset-release, +modernize-avoid-bind, +modernize-concat-nested-namespaces, +modernize-deprecated-headers, +modernize-deprecated-ios-base-aliases, +modernize-loop-convert, +modernize-make-shared, +modernize-make-unique, +modernize-pass-by-value, +modernize-raw-string-literal, +modernize-redundant-void-arg, +modernize-replace-auto-ptr, +modernize-replace-disallow-copy-and-assign-macro, +modernize-replace-random-shuffle, +modernize-return-braced-init-list, +modernize-shrink-to-fit, +modernize-unary-static-assert, +modernize-use-auto, +modernize-use-bool-literals, +modernize-use-emplace, +modernize-use-equals-default, +modernize-use-equals-delete, +modernize-use-nodiscard, +modernize-use-noexcept, +modernize-use-nullptr, +modernize-use-override, +modernize-use-transparent-functors, +modernize-use-uncaught-exceptions, +mpi-buffer-deref, +mpi-type-mismatch, +openmp-use-default-none, +performance-faster-string-find, +performance-for-range-copy, +performance-implicit-conversion-in-loop, +performance-inefficient-algorithm, +performance-inefficient-string-concatenation, +performance-inefficient-vector-operation, +performance-move-const-arg, +performance-move-constructor-init, +performance-no-automatic-move, +performance-noexcept-move-constructor, +performance-trivially-destructible, +performance-type-promotion-in-math-fn, +performance-unnecessary-copy-initialization, +performance-unnecessary-value-param, +portability-simd-intrinsics, +readability-avoid-const-params-in-decls, +readability-const-return-type, +readability-container-size-empty, +readability-convert-member-functions-to-static, +readability-delete-null-pointer, +readability-deleted-default, +readability-inconsistent-declaration-parameter-name, +readability-make-member-function-const, +readability-misleading-indentation, +readability-misplaced-array-index, +readability-non-const-parameter, +readability-redundant-control-flow, +readability-redundant-declaration, +readability-redundant-function-ptr-dereference, +readability-redundant-smartptr-get, +readability-redundant-string-cstr, +readability-redundant-string-init, +readability-simplify-subscript-expr, +readability-static-accessed-through-handle, +readability-static-definition-in-anonymous-namespace, +readability-string-compare, +readability-uniqueptr-delete-release, +readability-use-anyofallof' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..2dff2f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cmake-build-debug diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100755 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..77ce906 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,158 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100755 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100755 index 0000000..0310f9d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..206a77e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/point_cloud_viewer.iml b/.idea/point_cloud_viewer.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/point_cloud_viewer.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100755 index 0000000..f74a0f8 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..46d0684 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,196 @@ +cmake_minimum_required(VERSION 3.27) +project(z3d) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Ofast -pedantic -Wall -Werror ") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/=") + +add_executable(z3d main.cpp + include/assets/components/material_components.hpp + include/assets/components/point_cloud_vertex_components.hpp + include/assets/components/mesh_vertex_components.hpp + include/assets/dynamic_read_buffers/dynamic_material_buffer.hpp + include/assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp + include/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp + include/assets/data_loaders/kitti_loader.hpp + include/assets/data_loaders/obj_loader.hpp + include/assets/data_loaders/stl_loader.hpp + include/assets/data_loaders/uos_loader.hpp + include/assets/components/mesh_vertex_components.hpp + include/scene/camera.hpp + include/scene/flying_camera.hpp + include/opengl/handles/mesh_handle.hpp + include/opengl/handles/point_cloud_handle.hpp + include/opengl/handles/shader_program_handle.hpp + include/opengl/handles/texture_handle.hpp + include/opengl/data/mesh_data.hpp + include/opengl/data/point_cloud_data.hpp + include/opengl/data/shader_program_data.hpp + include/opengl/data/texture_data.hpp + include/scene/lighting_setup.hpp + include/rendering/batch_renderers/mesh_batch_renderer.hpp + include/opengl/error.hpp + include/rendering/batches/mesh_batch.hpp + include/rendering/batches/point_cloud_batch.hpp + include/util/arx.hpp + include/util/extra_arx_parsers.hpp + include/util/for_each.hpp + include/util/function.hpp + include/util/image.hpp + include/util/logger.hpp + include/util/string_indexer.hpp + include/util/string_literal.hpp + include/util/uix.hpp + source/assets/data_loaders/stl_loader.cpp + source/assets/data_loaders/obj_loader.cpp + source/scene/flying_camera.cpp + source/opengl/data/point_cloud_data.cpp + source/opengl/handles/shader_program_handle.ipp + source/opengl/data/texture_data.ipp + source/opengl/data/mesh_data.cpp + include/opengl/error.hpp + source/opengl/data/shader_program_data.cpp + source/opengl/handles/mesh_handle.ipp + source/opengl/handles/point_cloud_handle.ipp + source/opengl/handles/texture_handle.ipp + include/assets/data/surface_properties.hpp + include/util/unroll_bool_template.hpp + include/rendering/batch_renderers/point_cloud_batch_renderer.hpp + source/rendering/batches/mesh_batch.ipp + source/rendering/batches/point_cloud_batch.ipp + include/opengl/shader_program_variable.hpp + include/opengl/type_utils.hpp + include/util/binary_ifstream.hpp + source/assets/data_loaders/kitti_loader.cpp + include/assets/data_loaders/generic/generic_3dtk_loader.hpp + include/assets/data_loaders/uosr_loader.hpp + include/assets/data_loaders/uos_normal_loader.hpp + include/assets/data_loaders/uos_rgb_loader.hpp + include/assets/dynamic_data_loaders/dynamic_mesh_loader.hpp + source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp + source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp + include/assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp + include/assets/dynamic_data_loaders/generic/base_dynamic_loader.hpp + source/assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp + source/assets/dynamic_data_loaders/dynamic_point_cloud_loader.cpp + include/opengl/handles/matrix_handles.hpp + include/viewer/instance.hpp + include/opengl/data/material_data.hpp + include/opengl/handles/material_handle.hpp + include/opengl/handles/surface_properties_handle.hpp + include/opengl/handles/alpha_handle.hpp + source/opengl/data/material_data.ipp + include/util/specialised_lambda.hpp + include/viewer/asset_types.hpp + source/viewer/instance.cpp + include/viewer/asset_loader.hpp + source/viewer/asset_loader.cpp + include/assets/data_loaders/glsl_loader.hpp + include/assets/dynamic_read_buffers/dynamic_shader_buffer.hpp + source/assets/data_loaders/glsl_loader.cpp + include/viewer/settings.hpp + include/opengl/data/shader_data.hpp + source/opengl/data/shader_data.ipp + source/opengl/data/point_cloud_data.ipp + source/opengl/data/shader_data.cpp + include/opengl/handles/shader_handle.hpp + include/util/string_lookup.hpp + include/viewer/dynamic_shader_program_loading.hpp + source/viewer/dynamic_shader_program_loading.cpp + source/opengl/handles/shader_program_handle.cpp + include/opengl/shader_program_lookup.hpp + source/opengl/shader_program_lookup.cpp + include/shader_program/attributes/mesh_attributes.hpp + include/shader_program/attributes/point_cloud_attributes.hpp + include/shader_program/uniforms/mesh_uniforms.hpp + include/shader_program/uniforms/point_cloud_uniforms.hpp + include/shader_program/capabilities/mesh_capabilities.hpp + include/shader_program/capabilities/point_cloud_capabilities.hpp + include/rendering/requirements/mesh_requirements.hpp + include/rendering/requirements/point_cloud_requirements.hpp + include/rendering/modes/mesh_modes.hpp + include/rendering/modes/point_cloud_modes.hpp + source/rendering/batch_renderers/mesh_batch_renderer.cpp + source/rendering/batch_renderers/point_cloud_batch_renderer.cpp + include/rendering/shader_program_lookups/mesh_lookup.hpp + include/rendering/shader_program_lookups/mesh_lookup.hpp + source/rendering/shader_program_lookups/mesh_lookup.cpp + source/rendering/shader_program_lookups/point_cloud_lookup.cpp + include/rendering/shader_program_lookups/point_cloud_lookup.hpp + include/geometry/aabb.hpp + source/geometry/normal_estimation.cpp + include/geometry/normal_estimation.hpp + include/assets/components/texture_components.hpp + include/util/enum_operators.hpp + include/assets/dynamic_read_buffers/dynamic_texture_buffer.hpp + source/assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp + source/assets/dynamic_read_buffers/dynamic_material_buffer.ipp + include/assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp + source/assets/dynamic_read_buffers/dynamic_model_buffer.ipp + source/assets/dynamic_read_buffers/dynamic_texture_buffer.ipp + source/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp + include/assets/data_loaders/mtl_loader.hpp + include/assets/dynamic_data_loaders/dynamic_material_loader.hpp + include/util/id_type.hpp + include/assets/dynamic_data_stores/generic/generic_dynamic_store.hpp + source/assets/dynamic_data_stores/generic/generic_dynamic_store.ipp + include/assets/dynamic_data_stores/dynamic_material_store.hpp + include/assets/dynamic_data_stores/dynamic_mesh_store.hpp + include/assets/dynamic_data_stores/dynamic_point_cloud_store.hpp + include/util/result.hpp + source/assets/data_loaders/mtl_loader.cpp + include/util/line_parser.hpp + include/assets/dynamic_data_loaders/dynamic_texture_loader.hpp + source/assets/dynamic_data_loaders/dynamic_texture_loader.cpp + include/assets/dynamic_data_stores/dynamic_texture_store.hpp + include/opengl/data_uploaders/texture_data_uploader.hpp + include/assets/dynamic_data_stores/dynamic_vertex_store.hpp + include/assets/prefetch_lookups/mesh_prefetch_lookup.hpp + include/assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp + include/assets/prefetch_lookups/material_prefetch_lookup.hpp + include/assets/prefetch_lookups/texture_prefetch_lookup.hpp + include/assets/prefetch_lookups/pose_prefetch_lookup.hpp + include/assets/dynamic_data_stores/dynamic_pose_store.hpp + include/assets/data_loaders/kitti_pose_loader.hpp + include/assets/data_loaders/threedtk_pose_loader.hpp + source/assets/data_loaders/threedtk_pose_loader.cpp + include/assets/dynamic_read_buffers/dynamic_pose_buffer.hpp + source/assets/data_loaders/kitti_pose_loader.cpp + include/assets/prefetch_queue.hpp + include/util/string_list.hpp + include/assets/dynamic_data_stores/dynamic_material_library_store.hpp + include/assets/dynamic_data_stores/generic/generic_dynamic_component_store.hpp + source/assets/dynamic_data_stores/generic/generic_dynamic_component_store.ipp + source/assets/dynamic_data_stores/dynamic_material_store.cpp + include/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp + source/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp + source/assets/dynamic_data_stores/dynamic_mesh_store.cpp + include/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp + source/assets/dynamic_data_stores/dynamic_point_cloud_store.cpp + include/assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp + include/assets/dynamic_data_loaders/dynamic_material_library_loader.hpp + include/assets/prefetch_lookups/material_library_prefetch_lookup.hpp + include/assets/prefetch_lookups/shader_prefetch_lookup.hpp + include/assets/dynamic_data_stores/dynamic_shader_store.hpp + include/assets/data_loaders/generic/generic_3dtk_loader.hpp + source/assets/data_loaders/generic/generic_3dtk_loader.ipp + include/assets/prefetch_lookup.hpp + source/assets/prefetch_lookups/pose_prefetch_lookup.cpp + include/assets/dynamic_data_store.hpp +) + +target_include_directories(z3d PRIVATE include) +target_include_directories(z3d PRIVATE source) # for ipp headers +target_include_directories(z3d PRIVATE libraries/include/glm) +target_include_directories(z3d PRIVATE libraries/include/stb) + +find_package(GLEW REQUIRED) +find_package(OpenGL REQUIRED) +find_package(SFML REQUIRED COMPONENTS graphics system) +find_package(Eigen3 3.4 REQUIRED NO_MODULE) + +include_directories(${SFML_INCLUDE_DIR}) +include_directories(SYSTEM ${eigen_INCLUDE_DIR}) +target_link_libraries(z3d Eigen3::Eigen sfml-graphics sfml-system sfml-window ${OPENGL_LIBRARIES} ${GLEW_LIBRARIES}) diff --git a/data/fonts/JetBrainsMono_Medium.ttf b/data/fonts/JetBrainsMono_Medium.ttf new file mode 100755 index 0000000000000000000000000000000000000000..a6ba5529af2486ea7d2b92b7b8bd598bbee46e29 GIT binary patch literal 204140 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⩔2L2V?^8dEk_1{MYuW;QWxV?lFqK~+UT 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 literal 0 HcmV?d00001 diff --git a/include/assets/components/material_components.hpp b/include/assets/components/material_components.hpp new file mode 100644 index 0000000..e66dfbe --- /dev/null +++ b/include/assets/components/material_components.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include "../dynamic_read_buffers" +#include "assets/data/surface_properties.hpp" +#include "util/enum_operators.hpp" + +namespace components::material +{ +using surface_properties = ::surface_properties; +using transparency = float; +using ambient_color_texture = ::dynamic_texture_data; +using diffuse_color_texture = ::dynamic_texture_data; +using specular_color_texture = ::dynamic_texture_data; +using shininess_texture = ::dynamic_texture_data; +using alpha_texture = ::dynamic_texture_data; +using bump_texture = ::dynamic_texture_data; + +namespace indices +{ +using type = std::size_t; +inline constexpr type surface_properties = 0; +inline constexpr type transparency = 1; +inline constexpr type ambient_color_texture = 2; +inline constexpr type diffuse_color_texture = 3; +inline constexpr type specular_color_texture = 4; +inline constexpr type shininess_texture = 5; +inline constexpr type alpha_texture = 6; +inline constexpr type bump_texture = 7; +} + +enum class flags : std::uint8_t +{ + none = 0, + surface_properties = 1 << indices::surface_properties, + transparency = 1 << indices::transparency, + ambient_filter_texture = 1 << indices::ambient_color_texture, + diffuse_filter_texture = 1 << indices::diffuse_color_texture, + specular_filter_texture = 1 << indices::specular_color_texture, + shininess_texture = 1 << indices::shininess_texture, + alpha_texture = 1 << indices::alpha_texture, + bump_texture = 1 << indices::bump_texture +}; + +using all = std::tuple< + surface_properties, + transparency, + ambient_color_texture, + diffuse_color_texture, + specular_color_texture, + shininess_texture, + alpha_texture, + bump_texture +>; +constexpr inline auto count = std::tuple_size_v; + +} // namespace material_component + +DEFINE_ENUM_FLAG_OPERATORS(components::material::flags) diff --git a/include/assets/components/mesh_vertex_components.hpp b/include/assets/components/mesh_vertex_components.hpp new file mode 100755 index 0000000..ff578b8 --- /dev/null +++ b/include/assets/components/mesh_vertex_components.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include "util/enum_operators.hpp" + +namespace components::mesh_vertex { + +using position = std::array; +using normal = std::array; +using tex_coord = std::array; +using color = std::array; +using reflectance = std::array; + +namespace indices +{ +using type = std::size_t; +inline constexpr type position = 0; +inline constexpr type normal = 1; +inline constexpr type tex_coord = 2; +inline constexpr type color = 3; +inline constexpr type reflectance = 4; +} + +enum class flags : std::uint8_t { + none = 0, + position = 1 << indices::position, + normal = 1 << indices::normal, + tex_coord = 1 << indices::tex_coord, + color = 1 << indices::color, + reflectance = 1 << indices::reflectance +}; + +using all = std::tuple; +constexpr inline auto count = std::tuple_size_v; + +} // namespace components::mesh_vertex + +DEFINE_ENUM_FLAG_OPERATORS(components::mesh_vertex::flags) diff --git a/include/assets/components/point_cloud_vertex_components.hpp b/include/assets/components/point_cloud_vertex_components.hpp new file mode 100755 index 0000000..98e3ca1 --- /dev/null +++ b/include/assets/components/point_cloud_vertex_components.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include "util/enum_operators.hpp" + +namespace components::point_cloud_vertex { + +using position = std::array; +using normal = std::array; +using color = std::array; +using reflectance = std::array; + +namespace indices +{ +using type = std::size_t; +inline constexpr type position = 0; +inline constexpr type normal = 1; +inline constexpr type color = 2; +inline constexpr type reflectance = 3; +} // namespace indices + +enum class flags : std::uint8_t { + none = 0, + position = 1 << indices::position, + normal = 1 << indices::normal, + color = 1 << indices::color, + reflectance = 1 << indices::reflectance +}; + +using all = std::tuple; +constexpr inline auto count = std::tuple_size_v; + +} // namespace components::point_cloud_vertex + +DEFINE_ENUM_FLAG_OPERATORS(components::point_cloud_vertex::flags) diff --git a/include/assets/components/texture_components.hpp b/include/assets/components/texture_components.hpp new file mode 100644 index 0000000..f560d31 --- /dev/null +++ b/include/assets/components/texture_components.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "util/enum_operators.hpp" + +namespace components::texture { + +using red = std::uint8_t; +using green = std::uint8_t; +using blue = std::uint8_t; +using luminance = std::uint8_t; + +enum class flags : std::uint8_t { + none = 0, + luminance = 1 << 1, + red = 1 << 2, + green = 1 << 3, + blue = 1 << 4, + alpha = 1 << 5 +}; + +using all = std::tuple; +constexpr inline auto count = std::tuple_size_v; + +} // namespace components::texture + +DEFINE_ENUM_FLAG_OPERATORS(components::texture::flags) \ No newline at end of file diff --git a/include/assets/data/surface_properties.hpp b/include/assets/data/surface_properties.hpp new file mode 100644 index 0000000..dce0c1e --- /dev/null +++ b/include/assets/data/surface_properties.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +struct surface_properties +{ + std::array ambient_filter{ 0.7f, 0.7f, 0.7f }; + std::array diffuse_filter{ 0.466f, 0.466f, 0.7922f }; + std::array specular_filter{ 0.5974f, 0.2084f, 0.2084f }; + float shininess{ 100.2237f }; +}; \ No newline at end of file diff --git a/include/assets/data_loaders/glsl_loader.hpp b/include/assets/data_loaders/glsl_loader.hpp new file mode 100644 index 0000000..d585c09 --- /dev/null +++ b/include/assets/data_loaders/glsl_loader.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" +#include "assets/prefetch_lookups/mesh_prefetch_lookup.hpp" +#include "assets/dynamic_read_buffers/dynamic_shader_buffer.hpp" +#include "util/string_list.hpp" + +struct glsl_loader +{ + static constexpr auto name = std::string_view("glsl"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + dynamic_shader_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); +}; \ No newline at end of file diff --git a/include/assets/data_loaders/kitti_loader.hpp b/include/assets/data_loaders/kitti_loader.hpp new file mode 100644 index 0000000..3948380 --- /dev/null +++ b/include/assets/data_loaders/kitti_loader.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/components/point_cloud_vertex_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" +#include "glm/mat4x4.hpp" +#include "util/result.hpp" + +struct kitti_loader +{ + static constexpr auto name = std::string_view("kitti"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + dynamic_point_cloud_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); + +private: + inline static constexpr auto frame_folder = "frames"; + + [[nodiscard]] static ztu::result frame_id_from_filename( + std::string_view filename + ); + + [[nodiscard]] static std::error_code load_point_file( + const std::filesystem::path& filename, + dynamic_point_cloud_buffer& point_cloud + ); + + static void transform_point_cloud( + std::span points, + const glm::mat4& pose + ); + + [[nodiscard]] static ztu::result parent_directory(std::string_view path); + + +}; diff --git a/include/assets/data_loaders/kitti_pose_loader.hpp b/include/assets/data_loaders/kitti_pose_loader.hpp new file mode 100644 index 0000000..ce553c1 --- /dev/null +++ b/include/assets/data_loaders/kitti_pose_loader.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" +#include "util/string_list.hpp" +#include "assets/dynamic_data_stores/dynamic_pose_store.hpp" +#include "assets/dynamic_read_buffers/dynamic_pose_buffer.hpp" +#include "assets/prefetch_lookups/pose_prefetch_lookup.hpp" + +struct kitti_pose_loader +{ + static constexpr auto name = std::string_view("kitti_pose"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + dynamic_pose_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); + +private: + static constexpr auto pose_filename = std::string_view{ "pose.txt" }; + + static std::error_code parse_pose( + std::ifstream& in, + dynamic_pose_buffer& pose + ); +}; \ No newline at end of file diff --git a/include/assets/data_loaders/mtl_loader.hpp b/include/assets/data_loaders/mtl_loader.hpp new file mode 100644 index 0000000..dd2df5f --- /dev/null +++ b/include/assets/data_loaders/mtl_loader.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" +#include "assets/dynamic_read_buffers/dynamic_material_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_material_library_store.hpp" +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" +#include "util/string_lookup.hpp" +#include "util/result.hpp" + +namespace mtl_loader_error { + +enum class codes { + ok = 0, + mtl_cannot_open_file, + mtl_cannot_open_texture, + mtl_malformed_ambient_color, + mtl_malformed_diffuse_color, + mtl_malformed_specular_color, + mtl_malformed_specular_exponent, + mtl_malformed_dissolve, + mlt_unknown_line_begin +}; + +} // namespace mtl_loader_error + +class mtl_loader { +public: + static constexpr auto name = std::string_view("mtl"); + + std::optional find_id(std::string_view name); + + void clear_name_lookup(); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + // THis is not very elegant, but right now I do not see a better solution... + [[nodiscard]] static std::error_code load( + dynamic_material_library_buffer& material_library_buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); + + +private: + ztu::string_lookup m_id_lookup; +}; diff --git a/include/assets/data_loaders/obj_loader.hpp b/include/assets/data_loaders/obj_loader.hpp new file mode 100755 index 0000000..7bd95e6 --- /dev/null +++ b/include/assets/data_loaders/obj_loader.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include "assets/dynamic_data_loaders/dynamic_material_loader.hpp" +#include "assets/dynamic_data_stores/dynamic_mesh_store.hpp" +#include "assets/prefetch_lookup.hpp" + +namespace obj_loader_error { + +enum class codes { + ok = 0, + obj_cannot_open_file, + obj_malformed_vertex, + obj_malformed_texture_coordinate, + obj_malformed_normal, + obj_malformed_face, + obj_face_index_out_of_range, + obj_unknown_line_begin +}; + +} // namespace obj_loader_error + +struct obj_loader { + + static constexpr auto name = std::string_view("obj"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + components::mesh_vertex::flags enabled_components, + dynamic_mesh_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); +}; diff --git a/include/assets/data_loaders/stl_loader.hpp b/include/assets/data_loaders/stl_loader.hpp new file mode 100644 index 0000000..4c0b1f1 --- /dev/null +++ b/include/assets/data_loaders/stl_loader.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_mesh_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" + +struct stl_loader { + + static constexpr auto name = std::string_view("stl"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + // space stuff that has to persist + dynamic_mesh_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); +}; diff --git a/include/assets/data_loaders/threedtk_pose_loader.hpp b/include/assets/data_loaders/threedtk_pose_loader.hpp new file mode 100644 index 0000000..7365857 --- /dev/null +++ b/include/assets/data_loaders/threedtk_pose_loader.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "assets/dynamic_data_store.hpp" +#include "assets/prefetch_lookup.hpp" +#include "assets/prefetch_queue.hpp" +#include "assets/dynamic_read_buffers/dynamic_pose_buffer.hpp" +#include "util/string_list.hpp" +#include "util/result.hpp" +#include "assets/prefetch_lookups/pose_prefetch_lookup.hpp" + +struct threedtk_pose_loader +{ + static constexpr auto name = std::string_view("3dtk_pose"); + + [[nodiscard]] static std::error_code prefetch( + const file_dir_list& paths, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + dynamic_pose_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic = false + ); + +protected: + static std::error_code parse_transform_info( + std::ifstream& in, + std::string& line, + std::array& transform_info + ); + + static ztu::result parse_index( + std::string_view filename + ); +}; diff --git a/include/assets/data_loaders/uos_loader.hpp b/include/assets/data_loaders/uos_loader.hpp new file mode 100644 index 0000000..7d1e6ae --- /dev/null +++ b/include/assets/data_loaders/uos_loader.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "generic/generic_3dtk_loader.hpp" + +struct uos_loader : generic_3dtk_loader +{ + static constexpr auto name = std::string_view("uos"); +}; + diff --git a/include/assets/data_loaders/uos_normal_loader.hpp b/include/assets/data_loaders/uos_normal_loader.hpp new file mode 100644 index 0000000..5c95b04 --- /dev/null +++ b/include/assets/data_loaders/uos_normal_loader.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "generic/generic_3dtk_loader.hpp" + +struct uos_normal_loader : generic_3dtk_loader +{ + static constexpr auto name = std::string_view("uos_normal"); +}; diff --git a/include/assets/data_loaders/uos_rgb_loader.hpp b/include/assets/data_loaders/uos_rgb_loader.hpp new file mode 100644 index 0000000..f49b79e --- /dev/null +++ b/include/assets/data_loaders/uos_rgb_loader.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "generic/generic_3dtk_loader.hpp" + +struct uos_rgb_loader : generic_3dtk_loader +{ + static constexpr auto name = std::string_view("uos_rgb"); +}; diff --git a/include/assets/data_loaders/uosr_loader.hpp b/include/assets/data_loaders/uosr_loader.hpp new file mode 100644 index 0000000..c59f6e5 --- /dev/null +++ b/include/assets/data_loaders/uosr_loader.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "generic/generic_3dtk_loader.hpp" + +struct uosr_loader : generic_3dtk_loader +{ + static constexpr auto name = std::string_view("uosr"); +}; diff --git a/include/assets/dynamic_data_loaders/dynamic_material_library_loader.hpp b/include/assets/dynamic_data_loaders/dynamic_material_library_loader.hpp new file mode 100644 index 0000000..8c508ab --- /dev/null +++ b/include/assets/dynamic_data_loaders/dynamic_material_library_loader.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "assets/prefetch_queue.hpp" +#include "assets/components/material_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_material_library_store.hpp" +#include "assets/prefetch_lookups/material_library_prefetch_lookup.hpp" +#include "generic/base_dynamic_loader.hpp" + +#include "assets/data_loaders/mtl_loader.hpp" +#include "util/string_list.hpp" + +class dynamic_material_library_loader : public base_dynamic_loader< + components::material::flags, + mtl_loader +> { +public: + + [[nodiscard]] std::error_code prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue + ); + + [[nodiscard]] std::error_code load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_material_library_store& store, + dynamic_material_store& material_store, + material_library_prefetch_lookup& id_lookup, + bool pedantic = false + ); + +private: + dynamic_material_library_buffer m_buffer{}; +}; diff --git a/include/assets/dynamic_data_loaders/dynamic_material_loader.hpp b/include/assets/dynamic_data_loaders/dynamic_material_loader.hpp new file mode 100644 index 0000000..934f87f --- /dev/null +++ b/include/assets/dynamic_data_loaders/dynamic_material_loader.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "assets/prefetch_queue.hpp" +#include "assets/components/material_components.hpp" +#include "generic/base_dynamic_loader.hpp" + +#include "assets/data_loaders/mtl_loader.hpp" +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" +#include "assets/prefetch_lookups/material_prefetch_lookup.hpp" +#include "util/string_list.hpp" + +class dynamic_material_loader : public base_dynamic_loader< + components::material::flags + // TODO no loaders +> { +public: + + [[nodiscard]] std::error_code prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue + ); + + [[nodiscard]] std::error_code load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_material_store& store, + material_prefetch_lookup& id_lookup, + bool pedantic = false + ); + +private: + dynamic_material_buffer m_buffer{}; +}; diff --git a/include/assets/dynamic_data_loaders/dynamic_mesh_loader.hpp b/include/assets/dynamic_data_loaders/dynamic_mesh_loader.hpp new file mode 100644 index 0000000..3d02ed4 --- /dev/null +++ b/include/assets/dynamic_data_loaders/dynamic_mesh_loader.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "generic/base_dynamic_loader.hpp" +#include + +#include "assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_mesh_store.hpp" +#include "assets/data_loaders/obj_loader.hpp" +#include "assets/data_loaders/stl_loader.hpp" +#include "assets/prefetch_lookups/mesh_prefetch_lookup.hpp" + + +class dynamic_mesh_loader : public base_dynamic_loader< + components::mesh_vertex::flags, + obj_loader, + stl_loader +> { +public: + [[nodiscard]] std::error_code prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue + ); + + [[nodiscard]] std::error_code load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_mesh_store& store, + mesh_prefetch_lookup& id_lookup, + bool pedantic = false + ); + +private: + dynamic_mesh_buffer m_buffer{}; +}; diff --git a/include/assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp b/include/assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp new file mode 100644 index 0000000..7c7b601 --- /dev/null +++ b/include/assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "assets/prefetch_queue.hpp" +#include "generic/base_dynamic_loader.hpp" +#include "assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" + +#include "assets/data_loaders/kitti_loader.hpp" +#include "assets/data_loaders/uos_loader.hpp" +#include "assets/data_loaders/uos_normal_loader.hpp" +#include "assets/data_loaders/uos_rgb_loader.hpp" +#include "assets/data_loaders/uosr_loader.hpp" +#include "assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp" + +#include "util/string_list.hpp" + +class dynamic_point_cloud_loader : public base_dynamic_loader< + components::point_cloud_vertex::flags, + kitti_loader, + uos_loader, + uos_normal_loader, + uos_rgb_loader, + uos_loader, + uosr_loader +> { +public: + [[nodiscard]] std::error_code prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue + ); + + [[nodiscard]] std::error_code load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_point_cloud_store& store, + point_cloud_prefetch_lookup& id_lookup, + bool pedantic = false + ); + +private: + dynamic_point_cloud_buffer m_buffer{}; +}; diff --git a/include/assets/dynamic_data_loaders/dynamic_texture_loader.hpp b/include/assets/dynamic_data_loaders/dynamic_texture_loader.hpp new file mode 100644 index 0000000..a703253 --- /dev/null +++ b/include/assets/dynamic_data_loaders/dynamic_texture_loader.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include "util/uix.hpp" +#include "util/id_type.hpp" +#include "util/string_lookup.hpp" +#include "util/result.hpp" + +#include "assets/components/texture_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_texture_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_texture_store.hpp" +#include "assets/dynamic_data_loader_ctx.hpp" +#include "assets/prefetch_queue.hpp" +#include "assets/prefetch_lookups/texture_prefetch_lookup.hpp" +#include "util/string_list.hpp" + +/* +* [[nodiscard]] std::error_code prefetch( +loader_id_type loader_id, +const ztu::string_list& directories, +prefetch_queue& queue +); + +[[nodiscard]] std::error_code load( +loader_id_type loader_id, +const ztu::string_list& directories, +dynamic_point_cloud_store& store, +point_cloud_prefetch_lookup& id_lookup, +bool pedantic = false +); +*/ + +class dynamic_texture_loader +{ +public: + using loader_id_type = ztu::id_type_for; + + explicit dynamic_texture_loader(components::texture::flags enabled_components); + + [[nodiscard]] std::optional find_loader(const std::string_view& name); + + [[nodiscard]] std::error_code prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue + ); + + [[nodiscard]] std::error_code load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_texture_store& store, + texture_prefetch_lookup& id_lookup, + bool pedantic = false + ); + +private: + ztu::string_lookup m_loader_id_lookup{}; + components::texture::flags m_enabled_components{ components::texture::flags::none }; +}; diff --git a/include/assets/dynamic_data_loaders/generic/base_dynamic_loader.hpp b/include/assets/dynamic_data_loaders/generic/base_dynamic_loader.hpp new file mode 100644 index 0000000..bf0d819 --- /dev/null +++ b/include/assets/dynamic_data_loaders/generic/base_dynamic_loader.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" +#include "util/uix.hpp" +#include "util/string_lookup.hpp" +#include "util/id_type.hpp" +#include "util/result.hpp" + +template +class base_dynamic_loader +{ +public: + using loader_id_type = ztu::id_type_for; + + explicit base_dynamic_loader(C enabled_components); + + [[nodiscard]] std::optional find_loader(std::string_view name); + + [[nodiscard]] static consteval std::optional find_loader_static(std::string_view name); + + template + auto& get_loader(); + +protected: + + template + ztu::result invoke_with_matching_loader(loader_id_type loader_id, F&& f); + + std::tuple m_loaders{}; + ztu::string_lookup m_loader_id_lookup{}; + C m_enabled_components{ 0 }; +}; + +#define INCLUDE_BASE_DYNAMIC_LOADER_IMPLEMENTATION +#include "assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp" +#undef INCLUDE_BASE_DYNAMIC_LOADER_IMPLEMENTATION diff --git a/include/assets/dynamic_data_stores/dynamic_material_library_store.hpp b/include/assets/dynamic_data_stores/dynamic_material_library_store.hpp new file mode 100644 index 0000000..a180507 --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_material_library_store.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "generic/generic_dynamic_store.hpp" +#include "assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp" + +using dynamic_material_library_store = generic_dynamic_store; diff --git a/include/assets/dynamic_data_stores/dynamic_material_store.hpp b/include/assets/dynamic_data_stores/dynamic_material_store.hpp new file mode 100644 index 0000000..cfb849f --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_material_store.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "dynamic_texture_store.hpp" +#include "generic/generic_dynamic_component_store.hpp" +#include "assets/dynamic_read_buffers/dynamic_material_buffer.hpp" + +class dynamic_material_store { + using store_type = generic_dynamic_component_store< + components::material::flags, + components::material::surface_properties, + components::material::transparency, + dynamic_texture_store::id_type, + dynamic_texture_store::id_type, + dynamic_texture_store::id_type, + dynamic_texture_store::id_type, + dynamic_texture_store::id_type, + dynamic_texture_store::id_type + >; + +public: + using id_type = store_type::id_type; + using iterator_type = store_type::iterator_type; + using const_iterator = store_type::const_iterator; + + id_type add(const dynamic_material_buffer& data); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + +private: + store_type m_store; +}; diff --git a/include/assets/dynamic_data_stores/dynamic_mesh_store.hpp b/include/assets/dynamic_data_stores/dynamic_mesh_store.hpp new file mode 100644 index 0000000..2268d97 --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_mesh_store.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp" + +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp" + +class dynamic_mesh_store +{ + using store_type = generic_dynamic_indexed_component_array_store< + components::mesh_vertex::flags, + ztu::u32, + components::mesh_vertex::position, + components::mesh_vertex::normal, + components::mesh_vertex::tex_coord, + components::mesh_vertex::color, + components::mesh_vertex::reflectance + >; + +public: + using id_type = store_type::id_type; + using iterator_type = store_type::iterator_type; + using const_iterator = store_type::const_iterator; + + id_type add(const dynamic_mesh_buffer& mesh_buffer); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + +private: + store_type m_store; +}; \ No newline at end of file diff --git a/include/assets/dynamic_data_stores/dynamic_point_cloud_store.hpp b/include/assets/dynamic_data_stores/dynamic_point_cloud_store.hpp new file mode 100644 index 0000000..f86b9c2 --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_point_cloud_store.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp" + +#include "assets/components/point_cloud_vertex_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp" + + +class dynamic_point_cloud_store +{ + using store_type = generic_dynamic_component_array_store< + components::point_cloud_vertex::flags, + components::point_cloud_vertex::position, + components::point_cloud_vertex::normal, + components::point_cloud_vertex::color, + components::point_cloud_vertex::reflectance + >; + +public: + using id_type = store_type::id_type; + using iterator_type = store_type::iterator_type; + using const_iterator = store_type::const_iterator; + + id_type add(const dynamic_point_cloud_buffer& point_cloud_buffer); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + +private: + store_type m_store; +}; diff --git a/include/assets/dynamic_data_stores/dynamic_pose_store.hpp b/include/assets/dynamic_data_stores/dynamic_pose_store.hpp new file mode 100644 index 0000000..9c59d68 --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_pose_store.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "generic/generic_dynamic_store.hpp" +#include "glm/mat4x4.hpp" + +using dynamic_pose_store = generic_dynamic_store; diff --git a/include/assets/dynamic_data_stores/dynamic_shader_store.hpp b/include/assets/dynamic_data_stores/dynamic_shader_store.hpp new file mode 100644 index 0000000..1b88e2b --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_shader_store.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "generic/generic_dynamic_store.hpp" +#include "glm/mat4x4.hpp" + +class dynamic_shader_store { + + +}; \ No newline at end of file diff --git a/include/assets/dynamic_data_stores/dynamic_texture_store.hpp b/include/assets/dynamic_data_stores/dynamic_texture_store.hpp new file mode 100644 index 0000000..52437c3 --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_texture_store.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "generic/generic_dynamic_store.hpp" +#include "assets/dynamic_read_buffers/dynamic_texture_buffer.hpp" + +using dynamic_texture_store = generic_dynamic_store; diff --git a/include/assets/dynamic_data_stores/dynamic_vertex_store.hpp b/include/assets/dynamic_data_stores/dynamic_vertex_store.hpp new file mode 100644 index 0000000..77f0e5c --- /dev/null +++ b/include/assets/dynamic_data_stores/dynamic_vertex_store.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include "util/id_type.hpp" +#include "util/uix.hpp" + +#include "GL/glew.h" + +template +class dynamic_vertex_store { +public: + using id_type = ztu::id_type_for; + + void add( + C component_flags, + std::span... components + ); + + void build_vertex_buffer( + std::vector& vertex_buffer, + std::size_t& component_count, + std::array& component_types, + std::array& component_lengths, + GLsizei& stride + ) const; + +protected: + std::tuple...> m_component_buffers{}; + std::vector vertex_counts; + std::vector m_components{ 0 }; +}; + +#define INCLUDE_DYNAMIC_MODEL_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_model_buffer.ipp" +#undef INCLUDE_DYNAMIC_MODEL_DATA_IMPLEMENTATION diff --git a/include/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp b/include/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp new file mode 100644 index 0000000..94dec73 --- /dev/null +++ b/include/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "util/id_type.hpp" + +#include +#include +#include +#include +#include +#include +#include + +template +class generic_dynamic_component_array_store; + +template +class component_array_iterator { +public: + using value_type = std::tuple...>; + using size_type = std::size_t; + using count_type = ztu::u32; + using flag_count_type = std::pair; + using component_array_pointer_type = std::tuple...>; + using flag_count_pointer_type = const flag_count_type*; + using offsets_type = std::array; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + +private: + friend generic_dynamic_component_array_store; + + component_array_iterator( + component_array_pointer_type components, + flag_count_pointer_type flags, + std::size_t index, + const offsets_type& offsets + ); + +public: + constexpr component_array_iterator() noexcept = default; + + constexpr component_array_iterator(const component_array_iterator&) noexcept = default; + constexpr component_array_iterator(component_array_iterator&&) noexcept = default; + + constexpr component_array_iterator& operator=(const component_array_iterator&) noexcept = default; + constexpr component_array_iterator& operator=(component_array_iterator&&) noexcept = default; + + reference operator*() const; + + component_array_iterator& operator++(); + component_array_iterator operator++(int); + component_array_iterator& operator--(); + component_array_iterator operator--(int); + + component_array_iterator& operator+=(difference_type n); + component_array_iterator& operator-=(difference_type n); + component_array_iterator operator+(difference_type n) const; + component_array_iterator operator-(difference_type n) const; + difference_type operator-(const component_array_iterator& other) const; + + reference operator[](difference_type n) const; + + bool operator==(const component_array_iterator& other) const; + bool operator!=(const component_array_iterator& other) const; + bool operator<(const component_array_iterator& other) const; + bool operator<=(const component_array_iterator& other) const; + bool operator>(const component_array_iterator& other) const; + bool operator>=(const component_array_iterator& other) const; + +protected: + template + static bool is_component_enabled(C flag); + + template + void calc_offsets(std::index_sequence, difference_type n); + + template + reference dereference(std::index_sequence) const; + + template + std::tuple_element_t get_span() const; + +private: + value_type m_components{}; + const flag_count_type* m_flag_counts{}; + size_type m_index{}; + offsets_type m_offsets{}; +}; + +template +class generic_dynamic_component_array_store +{ +public: + using id_type = ztu::id_type_for; + using count_type = ztu::u32; + using iterator_type = component_array_iterator; + using const_iterator = component_array_iterator...>; + using view_type = std::ranges::subrange; + using const_view_type = std::ranges::subrange; + + id_type add(const std::tuple...>& component_arrays); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + + iterator_type begin(); + + iterator_type end(); + + const_iterator begin() const; + + const_iterator end() const; + + const_iterator cbegin() const; + + const_iterator cend() const; + + view_type view(); + + const_view_type view() const; + +protected: + std::tuple...> data_ptrs(); + + std::tuple>...> data_ptrs() const; + + std::array data_counts() const; + +private: + std::tuple...> m_component_arrays; + std::vector> m_component_flag_counts; + std::vector m_ids; + id_type m_next_data_id{ 1 }; +}; + +#define INCLUDE_GENERIC_DYNAMIC_COMPONENT_ARRAY_STORE_IMPLEMENTATION +#include "assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp" +#undef INCLUDE_GENERIC_DYNAMIC_COMPONENT_ARRAY_STORE_IMPLEMENTATION diff --git a/include/assets/dynamic_data_stores/generic/generic_dynamic_component_store.hpp b/include/assets/dynamic_data_stores/generic/generic_dynamic_component_store.hpp new file mode 100644 index 0000000..0dbac6f --- /dev/null +++ b/include/assets/dynamic_data_stores/generic/generic_dynamic_component_store.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "util/id_type.hpp" + +#include +#include +#include +#include +#include +#include +#include + +template +class generic_dynamic_component_store; + +template +class component_iterator { +public: + + + using value_type = std::tuple...>; + using size_type = std::size_t; + using offsets_type = std::array; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + +private: + friend generic_dynamic_component_store; + + component_iterator( + value_type components, + const C* flags, + std::size_t index, + const offsets_type& offsets + ); + +public: + + constexpr component_iterator() noexcept = default; + + constexpr component_iterator(const component_iterator&) noexcept = default; + constexpr component_iterator(component_iterator&&) noexcept = default; + + constexpr component_iterator& operator=(const component_iterator&) noexcept = default; + constexpr component_iterator& operator=(component_iterator&&) noexcept = default; + + reference operator*() const; + + component_iterator& operator++(); + component_iterator operator++(int); + component_iterator& operator--(); + component_iterator operator--(int); + + component_iterator& operator+=(difference_type n); + component_iterator& operator-=(difference_type n); + component_iterator operator+(difference_type n) const; + component_iterator operator-(difference_type n) const; + difference_type operator-(const component_iterator& other) const; + + reference operator[](difference_type n) const; + + bool operator==(const component_iterator& other) const; + bool operator!=(const component_iterator& other) const; + bool operator<(const component_iterator& other) const; + bool operator<=(const component_iterator& other) const; + bool operator>(const component_iterator& other) const; + bool operator>=(const component_iterator& other) const; + +protected: + template + static bool is_component_enabled(C flag); + + template + void calc_offsets(std::index_sequence, difference_type n); + + template + reference dereference(std::index_sequence) const; + + template + std::tuple_element_t get_pointer() const; + +private: + value_type m_components{}; + const C* m_flags{}; + size_type m_index{}; + offsets_type m_offsets{}; +}; + +template +class generic_dynamic_component_store +{ +public: + using id_type = ztu::id_type_for; + using iterator_type = component_iterator; + using const_iterator = component_iterator...>; + using view_type = std::ranges::subrange; + using const_view_type = std::ranges::subrange; + + id_type add(const std::tuple...>& data); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + + iterator_type begin(); + + iterator_type end(); + + const_iterator begin() const; + + const_iterator end() const; + + const_iterator cbegin() const; + + const_iterator cend() const; + + view_type view(); + + const_view_type view() const; + +protected: + std::tuple...> data_ptrs(); + + std::tuple>...> data_ptrs() const; + + std::array data_counts() const; + +private: + std::tuple...> m_components; + std::vector m_component_flags; + std::vector m_ids; + id_type m_next_data_id{ 1 }; +}; + +#define INCLUDE_GENERIC_DYNAMIC_COMPONENT_STORE_IMPLEMENTATION +#include "assets/dynamic_data_stores/generic_dynamic_component_store.ipp" +#undef INCLUDE_GENERIC_DYNAMIC_COMPONENT_STORE_IMPLEMENTATION diff --git a/include/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp b/include/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp new file mode 100644 index 0000000..8c47f0d --- /dev/null +++ b/include/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "util/id_type.hpp" + +#include +#include +#include +#include +#include +#include +#include + +template +class generic_dynamic_indexed_component_array_store; + +template +class indexed_component_array_iterator { +public: + using index_type = I; + using value_type = std::tuple, std::span...>; + using size_type = std::size_t; + using count_type = ztu::u32; + using flag_count_type = std::tuple; + using index_array_pointer_type = const index_type*; + using component_array_pointer_type = std::tuple...>; + using flag_count_pointer_type = const flag_count_type*; + + using offsets_type = std::array; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + +private: + friend generic_dynamic_indexed_component_array_store; + + indexed_component_array_iterator( + index_array_pointer_type indices, + const component_array_pointer_type& components, + flag_count_pointer_type flag_counts, + std::size_t index, + const offsets_type& offsets + ); + +public: + constexpr indexed_component_array_iterator() noexcept = default; + + constexpr indexed_component_array_iterator(const indexed_component_array_iterator&) noexcept = default; + constexpr indexed_component_array_iterator(indexed_component_array_iterator&&) noexcept = default; + + constexpr indexed_component_array_iterator& operator=(const indexed_component_array_iterator&) noexcept = default; + constexpr indexed_component_array_iterator& operator=(indexed_component_array_iterator&&) noexcept = default; + + reference operator*() const; + + indexed_component_array_iterator& operator++(); + indexed_component_array_iterator operator++(int); + indexed_component_array_iterator& operator--(); + indexed_component_array_iterator operator--(int); + + indexed_component_array_iterator& operator+=(difference_type n); + indexed_component_array_iterator& operator-=(difference_type n); + indexed_component_array_iterator operator+(difference_type n) const; + indexed_component_array_iterator operator-(difference_type n) const; + difference_type operator-(const indexed_component_array_iterator& other) const; + + reference operator[](difference_type n) const; + + bool operator==(const indexed_component_array_iterator& other) const; + bool operator!=(const indexed_component_array_iterator& other) const; + bool operator<(const indexed_component_array_iterator& other) const; + bool operator<=(const indexed_component_array_iterator& other) const; + bool operator>(const indexed_component_array_iterator& other) const; + bool operator>=(const indexed_component_array_iterator& other) const; + +protected: + template + static bool is_component_enabled(C flag); + + template + void calc_offsets(std::index_sequence, difference_type n); + + template + reference dereference(std::index_sequence) const; + +private: + index_array_pointer_type m_indices{}; + component_array_pointer_type m_components{}; + flag_count_pointer_type m_flag_counts{}; + size_type m_index{}; + offsets_type m_offsets{}; +}; + +template +class generic_dynamic_indexed_component_array_store +{ +public: + using id_type = ztu::id_type_for; + using count_type = ztu::u32; + using iterator_type = indexed_component_array_iterator; + using const_iterator = indexed_component_array_iterator...>; + using view_type = std::ranges::subrange; + using const_view_type = std::ranges::subrange; + + id_type add( + std::span indices, + const std::tuple...>& component_arrays + ); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + void remove(const iterator_type& it); + + void clear(); + + iterator_type begin(); + + iterator_type end(); + + const_iterator begin() const; + + const_iterator end() const; + + const_iterator cbegin() const; + + const_iterator cend() const; + + view_type view(); + + const_view_type view() const; + +protected: + + + std::tuple...> component_array_ptrs(); + + std::tuple>...> component_array_ptrs() const; + + std::array array_counts() const; + +private: + std::vector m_indices; + std::tuple...> m_component_arrays; + std::vector> m_component_flag_counts; + std::vector m_ids; + id_type m_next_data_id{ 1 }; +}; + +#define INCLUDE_GENERIC_DYNAMIC_INDEXED_COMPONENT_ARRAY_STORE_IMPLEMENTATION +#include "assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.ipp" +#undef INCLUDE_GENERIC_DYNAMIC_INDEXED_COMPONENT_ARRAY_STORE_IMPLEMENTATION diff --git a/include/assets/dynamic_data_stores/generic/generic_dynamic_store.hpp b/include/assets/dynamic_data_stores/generic/generic_dynamic_store.hpp new file mode 100644 index 0000000..ebb6f4f --- /dev/null +++ b/include/assets/dynamic_data_stores/generic/generic_dynamic_store.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "util/id_type.hpp" + +template +class generic_dynamic_store +{ +public: + using id_type = ztu::id_type_for; + using container_type = std::vector; + using iterator_type = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + id_type add(const T& data); + + [[nodiscard]] std::pair find(id_type id); + + [[nodiscard]] std::pair find(id_type id) const; + + [[nodiscard]] std::span data(); + + [[nodiscard]] std::span data() const; + + void remove(iterator_type it); + + void clear(); + +private: + std::vector m_data; + std::vector m_ids; + id_type m_next_data_id{ 1 }; +}; + +#define INCLUDE_GENERIC_DYNAMIC_STORE_IMPLEMENTATION +#include "assets/dynamic_data_stores/generic_dynamic_store.ipp" +#undef INCLUDE_GENERIC_DYNAMIC_STORE_IMPLEMENTATION diff --git a/include/assets/dynamic_read_buffers/dynamic_material_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_material_buffer.hpp new file mode 100755 index 0000000..d52e5b6 --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_material_buffer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "assets/components/material_components.hpp" +#include "assets/dynamic_data_stores/dynamic_texture_store.hpp" + +struct dynamic_material_buffer { + + dynamic_material_buffer() = default; + + components::material::surface_properties& initialized_surface_properties(); + + [[nodiscard]] std::optional& surface_properties(); + [[nodiscard]] std::optional& transparency(); + [[nodiscard]] std::optional& ambient_color_texture_id(); + [[nodiscard]] std::optional& diffuse_color_texture_id(); + [[nodiscard]] std::optional& specular_color_texture_id(); + [[nodiscard]] std::optional& shininess_texture_id(); + [[nodiscard]] std::optional& alpha_texture_id(); + [[nodiscard]] std::optional& bump_texture_id(); + + [[nodiscard]] const std::optional& surface_properties() const; + [[nodiscard]] const std::optional& transparency() const; + [[nodiscard]] const std::optional& ambient_color_texture_id() const; + [[nodiscard]] const std::optional& diffuse_color_texture_id() const; + [[nodiscard]] const std::optional& specular_color_texture_id() const; + [[nodiscard]] const std::optional& shininess_texture_id() const; + [[nodiscard]] const std::optional& alpha_texture_id() const; + [[nodiscard]] const std::optional& bump_texture_id() const; + + std::tuple< + std::optional, + std::optional, + std::optional, + std::optional, + std::optional, + std::optional, + std::optional, + std::optional + > data{ + std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt + }; +}; + +#define INCLUDE_DYNAMIC_MATERIAL_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_material_buffer.ipp" +#undef INCLUDE_DYNAMIC_MATERIAL_DATA_IMPLEMENTATION diff --git a/include/assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp new file mode 100644 index 0000000..13b03a6 --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_material_library_buffer.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "util/string_lookup.hpp" +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" + +using dynamic_material_library_buffer = ztu::string_lookup; diff --git a/include/assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp new file mode 100644 index 0000000..b8491aa --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_mesh_buffer.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" + +class dynamic_mesh_buffer : public dynamic_vertex_buffer< + components::mesh_vertex::flags, + components::mesh_vertex::position, + components::mesh_vertex::normal, + components::mesh_vertex::tex_coord, + components::mesh_vertex::color, + components::mesh_vertex::reflectance +> { +public: + using index_type = ztu::u32; + using triangle_type = std::array; + + [[nodiscard]] std::vector& positions(); + [[nodiscard]] std::vector& normals(); + [[nodiscard]] std::vector& tex_coords(); + [[nodiscard]] std::vector& colors(); + [[nodiscard]] std::vector& reflectances(); + [[nodiscard]] std::vector& triangles(); + [[nodiscard]] auto& material_id(); + + [[nodiscard]] const std::vector& positions() const; + [[nodiscard]] const std::vector& normals() const; + [[nodiscard]] const std::vector& tex_coords() const; + [[nodiscard]] const std::vector& colors() const; + [[nodiscard]] const std::vector& reflectances() const; + [[nodiscard]] const std::vector& triangles() const; + [[nodiscard]] const auto& material_id() const; + +private: + std::vector m_triangles{}; + dynamic_material_store::id_type m_material_id{}; +}; + +#define INCLUDE_DYNAMIC_MESH_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp" +#undef INCLUDE_DYNAMIC_MESH_DATA_IMPLEMENTATION diff --git a/include/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp new file mode 100644 index 0000000..3ff27b5 --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "assets/components/point_cloud_vertex_components.hpp" + +#include +#include +#include "assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp" + +class dynamic_point_cloud_buffer : public dynamic_vertex_buffer< + components::point_cloud_vertex::flags, + components::point_cloud_vertex::position, + components::point_cloud_vertex::normal, + components::point_cloud_vertex::color, + components::point_cloud_vertex::reflectance +> { +public: + [[nodiscard]] std::vector& positions(); + [[nodiscard]] std::vector& normals(); + [[nodiscard]] std::vector& colors(); + [[nodiscard]] std::vector& reflectances(); + + [[nodiscard]] const std::vector& positions() const; + [[nodiscard]] const std::vector& normals() const; + [[nodiscard]] const std::vector& colors() const; + [[nodiscard]] const std::vector& reflectances() const; +}; + +#define INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp" +#undef INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION \ No newline at end of file diff --git a/include/assets/dynamic_read_buffers/dynamic_pose_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_pose_buffer.hpp new file mode 100644 index 0000000..50181de --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_pose_buffer.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "glm/mat4x4.hpp" + +using dynamic_pose_buffer = glm::mat4; diff --git a/include/assets/dynamic_read_buffers/dynamic_shader_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_shader_buffer.hpp new file mode 100644 index 0000000..5fb00d6 --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_shader_buffer.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "GL/glew.h" + +struct dynamic_shader_buffer +{ + std::vector source{}; + GLenum type{ GL_INVALID_ENUM }; +}; diff --git a/include/assets/dynamic_read_buffers/dynamic_texture_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_texture_buffer.hpp new file mode 100755 index 0000000..267b97f --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_texture_buffer.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include "assets/components/texture_components.hpp" + +class dynamic_texture_buffer { +public: + using value_type = std::uint8_t; + using dim_type = std::int32_t; + using size_type = std::make_signed_t; + using difference_type = size_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = std::uint8_t*; + using const_pointer = const std::uint8_t*; + using iterator = pointer; + using const_iterator = const_pointer; + + dynamic_texture_buffer() = default; + + dynamic_texture_buffer( + std::unique_ptr&& data, + dim_type width, + dim_type height, + components::texture::flags components + ); + + dynamic_texture_buffer(const dynamic_texture_buffer&); + + dynamic_texture_buffer(dynamic_texture_buffer&&) noexcept; + + [[nodiscard]] dynamic_texture_buffer& operator=(const dynamic_texture_buffer&); + + [[nodiscard]] dynamic_texture_buffer& operator=(dynamic_texture_buffer&&) noexcept; + + [[nodiscard]] components::texture::flags components() const; + + [[nodiscard]] dim_type width() const; + + [[nodiscard]] dim_type height() const; + + [[nodiscard]] std::pair dimensions() const; + + [[nodiscard]] size_type pixel_count() const; + + [[nodiscard]] size_type component_count() const; + + [[nodiscard]] size_type size() const; + + [[nodiscard]] const_iterator begin() const; + + [[nodiscard]] iterator begin(); + + [[nodiscard]] const_iterator end() const; + + [[nodiscard]] iterator end(); + + [[nodiscard]] const_iterator cbegin() const; + + [[nodiscard]] const_iterator cend() const; + + [[nodiscard]] const_pointer data() const; + + [[nodiscard]] pointer data(); + +private: + std::unique_ptr m_data{ nullptr }; + dim_type m_width{ 0 }, m_height{ 0 }; + components::texture::flags m_components{ components::texture::flags::none }; +}; + +#define INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_texture_buffer.ipp" +#undef INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION diff --git a/include/assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp b/include/assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp new file mode 100644 index 0000000..31dd353 --- /dev/null +++ b/include/assets/dynamic_read_buffers/dynamic_vertex_buffer.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "util/uix.hpp" +#include +#include +#include "GL/glew.h" + +template +struct dynamic_vertex_buffer { + std::tuple...> vertices{}; +}; + +#define INCLUDE_DYNAMIC_MODEL_DATA_IMPLEMENTATION +#include "assets/dynamic_read_buffers/dynamic_texture_buffer.ipp" +#undef INCLUDE_DYNAMIC_MODEL_DATA_IMPLEMENTATION diff --git a/include/assets/prefetch_lookup.hpp b/include/assets/prefetch_lookup.hpp new file mode 100644 index 0000000..33c034c --- /dev/null +++ b/include/assets/prefetch_lookup.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "prefetch_lookups/material_library_prefetch_lookup.hpp" +#include "prefetch_lookups/material_prefetch_lookup.hpp" +#include "prefetch_lookups/mesh_prefetch_lookup.hpp" +#include "prefetch_lookups/point_cloud_prefetch_lookup.hpp" +#include "prefetch_lookups/pose_prefetch_lookup.hpp" +#include "prefetch_lookups/shader_prefetch_lookup.hpp" +#include "prefetch_lookups/texture_prefetch_lookup.hpp" + +struct prefetch_lookup +{ + texture_prefetch_lookup textures; + material_library_prefetch_lookup material_libraries; + material_prefetch_lookup materials; + mesh_prefetch_lookup meshes; + pose_prefetch_lookup poses; + point_cloud_prefetch_lookup point_clouds; + shader_prefetch_lookup shaders; +}; \ No newline at end of file diff --git a/include/assets/prefetch_lookups/material_library_prefetch_lookup.hpp b/include/assets/prefetch_lookups/material_library_prefetch_lookup.hpp new file mode 100644 index 0000000..e4b1a71 --- /dev/null +++ b/include/assets/prefetch_lookups/material_library_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_material_library_store.hpp" + +using material_library_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_lookups/material_prefetch_lookup.hpp b/include/assets/prefetch_lookups/material_prefetch_lookup.hpp new file mode 100644 index 0000000..efe5bdd --- /dev/null +++ b/include/assets/prefetch_lookups/material_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" + +using material_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_lookups/mesh_prefetch_lookup.hpp b/include/assets/prefetch_lookups/mesh_prefetch_lookup.hpp new file mode 100644 index 0000000..3e60670 --- /dev/null +++ b/include/assets/prefetch_lookups/mesh_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_mesh_store.hpp" + +using mesh_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp b/include/assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp new file mode 100644 index 0000000..1eef821 --- /dev/null +++ b/include/assets/prefetch_lookups/point_cloud_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" + +using point_cloud_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_lookups/pose_prefetch_lookup.hpp b/include/assets/prefetch_lookups/pose_prefetch_lookup.hpp new file mode 100644 index 0000000..5204764 --- /dev/null +++ b/include/assets/prefetch_lookups/pose_prefetch_lookup.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include "assets/dynamic_data_stores/dynamic_pose_store.hpp" +#include "util/uix.hpp" + +class pose_prefetch_lookup { +public: + using index_type = ztu::u32; + using directory_lookup = std::unordered_map; + using directory_iterator = std::pair; + using index_lookup = std::vector; + using index_iterator = index_lookup::iterator; + using lookup_type = std::vector; + using iterator = lookup_type::iterator; + + void emplace( + const std::filesystem::path& directory, + index_type index, + dynamic_pose_store::id_type id + ); + + void emplace_hint_dir( + directory_iterator directory_it, + const std::filesystem::path& directory, + index_type index, + dynamic_pose_store::id_type id + ); + + void emplace_hint_dir_index( + directory_iterator directory_it, + index_iterator index_it, + index_type index, + dynamic_pose_store::id_type id + ); + + std::pair find_directory( + const std::filesystem::path& directory + ); + + std::pair find_index( + directory_iterator directory_it, + index_type index + ); + +protected: + directory_iterator emplace_dir( + directory_iterator directory_it, + const std::filesystem::path& directory + ); + +private: + directory_lookup m_directory_lookup; + index_lookup m_directory_indices; // count before indices, indices sorted per dir + lookup_type m_pose_ids; // offset by 1 +}; diff --git a/include/assets/prefetch_lookups/shader_prefetch_lookup.hpp b/include/assets/prefetch_lookups/shader_prefetch_lookup.hpp new file mode 100644 index 0000000..7cdd980 --- /dev/null +++ b/include/assets/prefetch_lookups/shader_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_shader_st + +using texture_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_lookups/texture_prefetch_lookup.hpp b/include/assets/prefetch_lookups/texture_prefetch_lookup.hpp new file mode 100644 index 0000000..c345f9d --- /dev/null +++ b/include/assets/prefetch_lookups/texture_prefetch_lookup.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "assets/dynamic_data_stores/dynamic_texture_store.hpp" + +using texture_prefetch_lookup = std::unordered_map; diff --git a/include/assets/prefetch_queue.hpp b/include/assets/prefetch_queue.hpp new file mode 100644 index 0000000..da77caa --- /dev/null +++ b/include/assets/prefetch_queue.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "util/string_list.hpp" + +struct file_dir_list +{ + ztu::string_list files; + ztu::string_list directories; +}; + +struct prefetch_queue +{ + file_dir_list obj_queue; + file_dir_list stl_queue; + + file_dir_list mtl_queue; + + file_dir_list uosr_queue; + file_dir_list uos_rgb_queue; + file_dir_list uos_normal_queue; + file_dir_list uos_queue; + file_dir_list kitti_queue; + + file_dir_list threedtk_pose_queue; + file_dir_list kitti_pose_queue; + + file_dir_list glsl_queue; +}; \ No newline at end of file diff --git a/include/opengl/data/material_data.hpp b/include/opengl/data/material_data.hpp new file mode 100644 index 0000000..8322263 --- /dev/null +++ b/include/opengl/data/material_data.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "assets/data/texture.hpp" +#include "assets/data/surface_properties.hpp" +#include "assets/components/material_components.hpp" + +#include "opengl/handles/material_handle.hpp" +#include "opengl/data/texture_data.hpp" + +#include + + +namespace zgl +{ +struct material_data +{ +private: + material_data( + const std::optional& texture_handle, + const std::optional& surface_properties_handle, + const std::optional& alpha_handle, + std::optional&& texture_data, + components::material::flags components + ); + +public: + material_data() = default; + + [[nodiscard]] static std::error_code build_from( + const std::optional& texture_opt, + const std::optional& surface_properties_opt, + const std::optional& transparency_opt, + material_component::flags components, + material_data& data + ); + + material_data(const material_data& other) = delete; + material_data& operator=(const material_data& other) = delete; + + material_data(material_data&& other) noexcept; + material_data& operator=(material_data&& other) noexcept; + + [[nodiscard]] material_handle handle() const; + + [[nodiscard]] material_component::flags components() const; + +private: + material_handle m_handle{}; + std::optional m_texture_data{ std::nullopt }; + material_component::flags m_component_types{ + material_component::flags::none + }; +}; +} + +#define INCLUDE_MATERIAL_DATA_IMPLEMENTATION +#include "opengl/data/material_data.ipp" +#undef INCLUDE_MATERIAL_DATA_IMPLEMENTATION diff --git a/include/opengl/data/mesh_data.hpp b/include/opengl/data/mesh_data.hpp new file mode 100755 index 0000000..734261a --- /dev/null +++ b/include/opengl/data/mesh_data.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "opengl/handles/mesh_handle.hpp" +#include "util/uix.hpp" + +#include +#include + +#include "assets/components/mesh_vertex_components.hpp" +#include "GL/glew.h" + +namespace zgl +{ +struct mesh_data +{ +private: + mesh_data( + GLuint vertex_vbo_id, + GLuint index_vbo_id, + GLuint vao_id, + ztu::u32 material_id, + components::mesh_vertex::flags components, + GLsizei index_count + ); + +public: + mesh_data() = default; + + [[nodiscard]] static std::error_code build_from( + std::span vertex_buffer, + std::span component_types, + std::span component_lengths, + GLsizei stride, + std::span index_buffer, + ztu::u32 material_id, + components::mesh_vertex::flags components, + mesh_data& data + ); + + mesh_data(const mesh_data& other) = delete; + mesh_data& operator=(const mesh_data& other) = delete; + + mesh_data(mesh_data&& other) noexcept; + mesh_data& operator=(mesh_data&& other) noexcept; + + ~mesh_data(); + + [[nodiscard]] mesh_handle handle() const; + + [[nodiscard]] components::mesh_vertex::flags components() const; + + [[nodiscard]] ztu::u32 material_id() const; + + +private: + mesh_handle m_handle{}; + GLuint m_vertex_vbo_id{ 0 }; + GLuint m_index_vbo_id{ 0 }; + ztu::u32 m_material_id{ 0 }; + components::mesh_vertex::flags m_component_types{ + components::mesh_vertex::flags::none + }; +}; +} + +#define INCLUDE_MESH_DATA_IMPLEMENTATION +#include "opengl/data/mesh_data.ipp" +#undef INCLUDE_MESH_DATA_IMPLEMENTATION diff --git a/include/opengl/data/point_cloud_data.hpp b/include/opengl/data/point_cloud_data.hpp new file mode 100644 index 0000000..9beeaee --- /dev/null +++ b/include/opengl/data/point_cloud_data.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "util/uix.hpp" + +#include +#include +#include "GL/glew.h" +#include "opengl/handles/point_cloud_handle.hpp" +#include "assets/components/point_cloud_vertex_components.hpp" + + +namespace zgl { +struct point_cloud_data { +private: + point_cloud_data( + GLuint vertex_buffer_id, + GLuint vao_id, + GLsizei point_count + ); + +public: + point_cloud_data() = default; + + [[nodiscard]] static std::error_code build_from( + std::span point_buffer, + std::span component_types, + std::span component_lengths, + GLsizei stride, + point_cloud_data& data + ); + + point_cloud_data(const point_cloud_data& other) = delete; + point_cloud_data& operator=(const point_cloud_data& other) = delete; + + point_cloud_data(point_cloud_data&& other) noexcept; + point_cloud_data& operator=(point_cloud_data&& other) noexcept; + + ~point_cloud_data(); + + [[nodiscard]] point_cloud_handle handle() const; + + [[nodiscard]] components::point_cloud_vertex::flags components() const; + +private: + point_cloud_handle m_handle{}; + GLuint m_vertex_vbo_id{ 0 }; + components::point_cloud_vertex::flags m_component_types{ + components::point_cloud_vertex::flags::none + }; +}; +} + +#define INCLUDE_POINT_CLOUD_DATA_IMPLEMENTATION +#include "opengl/data/point_cloud_data.ipp" +#undef INCLUDE_POINT_CLOUD_DATA_IMPLEMENTATION \ No newline at end of file diff --git a/include/opengl/data/shader_data.hpp b/include/opengl/data/shader_data.hpp new file mode 100644 index 0000000..72a3791 --- /dev/null +++ b/include/opengl/data/shader_data.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include "GL/glew.h" +#include "opengl/handles/shader_handle.hpp" + +namespace zgl +{ +class shader_data +{ +private: + explicit shader_data(GLuint shader_id, GLenum type); + +public: + + shader_data() = default; + + [[nodiscard]] static std::error_code build_from( + GLenum type, + const std::string& source, + shader_data& data + ); + + shader_data(const shader_data& other) = delete; + shader_data& operator=(const shader_data& other) = delete; + + shader_data(shader_data&& other) noexcept; + shader_data& operator=(shader_data&& other) noexcept; + + [[nodiscard]] shader_handle handle() const; + + ~shader_data(); + +private: + shader_handle m_handle{}; + GLenum m_type{ GL_INVALID_ENUM }; +}; +} + +#define INCLUDE_SHADER_DATA_IMPLEMENTATION +#include "opengl/data/shader_data.ipp" +#undef INCLUDE_SHADER_DATA_IMPLEMENTATION diff --git a/include/opengl/data/texture_data.hpp b/include/opengl/data/texture_data.hpp new file mode 100644 index 0000000..44f6d37 --- /dev/null +++ b/include/opengl/data/texture_data.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "GL/glew.h" +#include "opengl/handles/texture_handle.hpp" + +#include + +namespace zgl +{ +struct texture_data +{ +private: + explicit texture_data(GLuint texture_id); + +public: + texture_data() = default; + + template + [[nodiscard]] static std::error_code build_from( + std::span buffer, + GLenum format, + GLenum type, + GLsizei width, + GLsizei height, + texture_data& data + ); + + texture_data(const texture_data&) = delete; + texture_data& operator=(const texture_data&) = delete; + + texture_data(texture_data&&) noexcept; + texture_data& operator=(texture_data&&) noexcept; + + ~texture_data(); + + [[nodiscard]] texture_handle handle() const; + +private: + texture_handle m_handle{}; +}; +} + +#define INCLUDE_TEXTURE_DATA_IMPLEMENTATION +#include "opengl/data/texture_data.ipp" +#undef INCLUDE_TEXTURE_DATA_IMPLEMENTATION diff --git a/include/opengl/data_uploaders/texture_data_uploader.hpp b/include/opengl/data_uploaders/texture_data_uploader.hpp new file mode 100644 index 0000000..be5c621 --- /dev/null +++ b/include/opengl/data_uploaders/texture_data_uploader.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "../../assets/dynamic_read_buffers" +#include "assets/dynamic_data_stores/dynamic_texture_store.hpp" +#include "opengl/data/texture_data.hpp" +#include + +namespace zgl +{ +class texture_data_uploader +{ + + void upload( + std::span dynamic_data, + std::span dynamic_data_ids + ) { + + std::vector texture_ids; + std::vector invalid_texture_ids; + + texture_ids.resize(dynamic_data.size()); + + glGenTextures(texture_ids.size(), texture_ids.data()); + + auto texture_id_it = texture_ids.begin(); + + for (std::size_t i{}; i != dynamic_data.size(); ++i) + { + const auto& texture_id = *texture_id_it; + const auto& texture = dynamic_data[i]; + + glBindTexture(GL_TEXTURE_2D, texture_id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + GLenum format; + switch (texture.components()) { + using enum components::texture::flags; + case luminance: + format = GL_LUMINANCE; + break; + case luminance | alpha: + format = GL_LUMINANCE_ALPHA; + break; + case red | green | blue: + format = GL_RGB; + break; + case red | green | blue | alpha: + format = GL_RGBA; + break; + default: + format = GL_INVALID_ENUM; + break; + } + + if (format == GL_INVALID_ENUM) + { + invalid_texture_ids.push_back(texture_id); + } + else + { + glTexImage2D( + GL_TEXTURE_2D, 0, + GL_RGBA8, + texture.width(), + texture.height(), + 0, + format, + GL_UNSIGNED_BYTE, + texture.data() + ); + glGenerateMipmap(GL_TEXTURE_2D); + } + } + + glBindTexture(GL_TEXTURE_2D, 0); + + glDeleteTextures(invalid_texture_ids.size(), invalid_texture_ids.data()); + } +}; +} diff --git a/include/opengl/handles/shader_handle.hpp b/include/opengl/handles/shader_handle.hpp new file mode 100644 index 0000000..a6aeee5 --- /dev/null +++ b/include/opengl/handles/shader_handle.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "GL/glew.h" + +namespace zgl +{ +struct shader_handle +{ + constexpr bool operator==(const shader_handle&) const = default; + + GLuint shader_id{ 0 }; +}; +} \ No newline at end of file diff --git a/include/opengl/shader_program_lookup.hpp b/include/opengl/shader_program_lookup.hpp new file mode 100644 index 0000000..b6b0061 --- /dev/null +++ b/include/opengl/shader_program_lookup.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "opengl/handles/shader_program_handle.hpp" +#include +#include + +namespace zgl { +class shader_program_lookup +{ +public: + void add( + const shader_program_handle& shader_program_handle, + std::span all_attributes, + std::span all_uniforms + ); + + [[nodiscard]] std::optional find( + shader_program_handle::attribute_support_type attributes, + shader_program_handle::uniform_support_type uniforms, + std::span all_attributes + ) const; + + void print(); + +private: + using attribute_locations_type = ztu::u32; + + [[nodiscard]] static attribute_locations_type attribute_location_flags( + shader_program_handle::attribute_support_type attributes, + std::span all_attributes + ); + + struct attribute_entry_type + { + shader_program_handle::attribute_support_type attributes; + attribute_locations_type locations; // Do not go past location 31!!! + }; + + std::vector m_mesh_shader_program_uniforms; + std::vector m_mesh_shader_program_attributes; + std::vector m_mesh_shader_programs; +}; +} diff --git a/include/rendering/batch_renderers/mesh_batch_renderer.hpp b/include/rendering/batch_renderers/mesh_batch_renderer.hpp new file mode 100644 index 0000000..f8f6fd0 --- /dev/null +++ b/include/rendering/batch_renderers/mesh_batch_renderer.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "opengl/handles/shader_program_handle.hpp" +#include "rendering/batches/mesh_batch.hpp" +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/components/material_components.hpp" +#include "opengl/handles/material_handle.hpp" +#include + +#include "geometry/aabb.hpp" +#include "rendering/modes/mesh_modes.hpp" +#include "rendering/shader_program_lookups/mesh_lookup.hpp" +#include "scene/lighting_setup.hpp" + +namespace rendering +{ +class mesh_batch_renderer +{ +public: + using batch_components_type = std::pair< + components::mesh_vertex::flags, + material_component::flags + >; + using batch_type = mesh_batch; + using batch_index_type = ztu::u32; + using batch_id_type = batch_index_type; + using id_type = std::pair; + + explicit mesh_batch_renderer(int render_mode_count); + + std::optional add( + const batch_components_type& batch_component, + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const zgl::material_handle& material, + const shader_program_lookups::mesh_lookup& shader_program_lookup + ); + + std::optional bounding_box(id_type); + + bool remove(id_type mesh_id); + + void render( + modes::mesh render_mode, + const glm::mat4& vp_matrix, + const glm::mat4& view_matrix, + const glm::vec3& view_pos, + const lighting_setup& lights + ); + +protected: + [[nodiscard]] std::pair lookup_batch(const batch_components_type& batch_components) const; + +private: + int m_render_mode_count; + + std::vector> m_component_lookup{}; + std::vector m_id_lookup{}; + std::vector> m_batches{}; + std::vector m_shader_programs{}; + + batch_id_type m_next_batch_id{ 0 }; +}; +} diff --git a/include/rendering/batch_renderers/point_cloud_batch_renderer.hpp b/include/rendering/batch_renderers/point_cloud_batch_renderer.hpp new file mode 100644 index 0000000..ee9eecc --- /dev/null +++ b/include/rendering/batch_renderers/point_cloud_batch_renderer.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "opengl/handles/shader_program_handle.hpp" +#include "rendering/batches/point_cloud_batch.hpp" +#include "assets/components/point_cloud_vertex_components.hpp" +#include "rendering/modes/point_cloud_modes.hpp" +#include + +#include "scene/lighting_setup.hpp" +#include "rendering/shader_program_lookups/point_cloud_lookup.hpp" + +namespace rendering +{ + +class point_cloud_batch_renderer +{ +public: + using batch_components_type = components::point_cloud_vertex::flags; + using batch_type = point_cloud_batch; + using batch_index_type = ztu::u32; + using batch_id_type = batch_index_type; + using id_type = std::pair; + + explicit point_cloud_batch_renderer(int render_mode_count); + + std::optional add( + batch_components_type batch_components, + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const shader_program_lookups::point_cloud_lookup& shader_program_lookup + ); + + std::optional bounding_box(id_type id); + + bool remove(id_type id); + + void render( + modes::point_cloud render_mode, + const glm::mat4& vp_matrix, + const glm::vec3& camera_position, + const lighting_setup& + ); + +protected: + [[nodiscard]] std::pair lookup_batch(const batch_components_type& batch_component) const; + +private: + int m_render_mode_count; + + std::vector> m_component_lookup; + std::vector m_id_lookup; + std::vector> m_batches; + std::vector m_shader_programs; + + batch_id_type m_next_batch_id{ 0 }; +}; + +} diff --git a/include/rendering/batches/mesh_batch.hpp b/include/rendering/batches/mesh_batch.hpp new file mode 100644 index 0000000..f5cbb87 --- /dev/null +++ b/include/rendering/batches/mesh_batch.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include "opengl/handles/mesh_handle.hpp" +#include "opengl/handles/matrix_handles.hpp" +#include "opengl/handles/texture_handle.hpp" +#include "opengl/handles/mesh_handle.hpp" +#include "opengl/handles/material_handle.hpp" +#include "geometry/aabb.hpp" + +class mesh_batch +{ +public: + using id_type = ztu::u32; + + inline id_type add( + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const zgl::material_handle& material + ); + + inline std::optional bounding_box(id_type id); + + inline bool remove(id_type id); + + [[nodiscard]] inline std::span meshes() const; + + [[nodiscard]] inline std::span bounding_boxes() const; + + [[nodiscard]] inline std::span transforms() const; + + [[nodiscard]] inline std::span textures() const; + + [[nodiscard]] inline std::span surface_properties() const; + + [[nodiscard]] inline std::span alphas() const; + +private: + std::vector m_meshes{}; + std::vector m_bounding_boxes{}; + std::vector m_transforms{}; + std::vector m_textures{}; + std::vector m_surface_properties{}; + std::vector m_alphas{}; + + std::vector m_id_lookup{}; + + id_type m_next_mesh_id{ 0 }; +}; + +#define INCLUDE_MESH_BATCH_IMPLEMENTATION +#include "rendering/batches/mesh_batch.ipp" +#undef INCLUDE_MESH_BATCH_IMPLEMENTATION diff --git a/include/rendering/batches/point_cloud_batch.hpp b/include/rendering/batches/point_cloud_batch.hpp new file mode 100644 index 0000000..4c3a2d4 --- /dev/null +++ b/include/rendering/batches/point_cloud_batch.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "opengl/handles/point_cloud_handle.hpp" +#include "opengl/handles/matrix_handles.hpp" +#include + +#include "geometry/aabb.hpp" +#include + +class point_cloud_batch +{ +public: + using id_type = ztu::u32; + + point_cloud_batch() = default; + + inline id_type add( + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform + ); + + inline std::optional bounding_box(id_type id); + + inline bool remove(id_type id); + + [[nodiscard]] inline std::span point_clouds() const; + + [[nodiscard]] inline std::span transforms() const; + + [[nodiscard]] inline std::span bounding_boxes() const; + +private: + std::vector m_point_clouds{}; + std::vector m_transforms{}; + std::vector m_bounding_boxes{}; + + std::vector m_id_lookup{}; + + id_type m_next_id{ 0 }; +}; + +#define INCLUDE_POINT_CLOUD_BATCH_IMPLEMENTATION +#include "rendering/batches/point_cloud_batch.ipp" +#undef INCLUDE_POINT_CLOUD_BATCH_IMPLEMENTATION diff --git a/include/rendering/modes/mesh_modes.hpp b/include/rendering/modes/mesh_modes.hpp new file mode 100644 index 0000000..b3038f3 --- /dev/null +++ b/include/rendering/modes/mesh_modes.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace rendering::modes +{ +enum class mesh +{ + wire_frame, + points, + faces, + lit_faces, + count +}; +}; diff --git a/include/rendering/modes/point_cloud_modes.hpp b/include/rendering/modes/point_cloud_modes.hpp new file mode 100644 index 0000000..0531c5c --- /dev/null +++ b/include/rendering/modes/point_cloud_modes.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace rendering::modes +{ +enum class point_cloud +{ + uniform_color, + rainbow, + count +}; +} diff --git a/include/rendering/requirements/mesh_requirements.hpp b/include/rendering/requirements/mesh_requirements.hpp new file mode 100644 index 0000000..fbe515d --- /dev/null +++ b/include/rendering/requirements/mesh_requirements.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include "assets/components/material_components.hpp" +#include "assets/components/mesh_vertex_components.hpp" +#include "shader_program/capabilities/mesh_capabilities.hpp" + +#include + +namespace rendering::requirements::mesh +{ + +struct type +{ + shader_program::capabilities::mesh::indices::type shader_program_requirement_index{}; + components::mesh_vertex::flags vertex_requirements{ + components::mesh_vertex::flags::none + }; + material_component::flags material_requirements{ + material_component::flags::none + }; +}; + +enum class flags : int +{ + none = 0, + position = 1 << 0, + lit = 1 << 1, + textured = 1 << 2, + uniform_color = 1 << 3, + uniform_alpha = 1 << 4, + point = 1 << 5 +}; + +constexpr inline auto position = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::position, + .vertex_requirements = components::mesh_vertex::flags::position +}; + +constexpr inline auto lit = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::lit, + .vertex_requirements = components::mesh_vertex::flags::normal, + .material_requirements = material_component::flags::surface_properties +}; + +constexpr inline auto point = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::point +}; + +constexpr inline auto textured = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::textured, + .vertex_requirements = components::mesh_vertex::flags::tex_coord, + .material_requirements = material_component::flags::texture, +}; + +constexpr inline auto uniform_color = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::uniform_color +}; + +constexpr inline auto uniform_alpha = type{ + .shader_program_requirement_index = shader_program::capabilities::mesh::indices::uniform_alpha, + .material_requirements = material_component::flags::transparency +}; + +constexpr inline auto all = std::array{ + position, lit, textured, uniform_color, uniform_alpha, point +}; + +} + + +[[nodiscard]] constexpr rendering::requirements::mesh::flags operator|( + const rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::mesh::flags operator&( + const rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::mesh::flags operator^( + const rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::mesh::flags operator~(const rendering::requirements::mesh::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr rendering::requirements::mesh::flags& operator|=(rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b) { + return a = a | b; +} + +constexpr rendering::requirements::mesh::flags& operator&=(rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b) { + return a = a & b; +} + +constexpr rendering::requirements::mesh::flags& operator^=(rendering::requirements::mesh::flags& a, const rendering::requirements::mesh::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + rendering::requirements::mesh::flags lhs, rendering::requirements::mesh::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + rendering::requirements::mesh::flags lhs, rendering::requirements::mesh::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + rendering::requirements::mesh::flags lhs, rendering::requirements::mesh::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + rendering::requirements::mesh::flags lhs, rendering::requirements::mesh::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/rendering/requirements/point_cloud_requirements.hpp b/include/rendering/requirements/point_cloud_requirements.hpp new file mode 100644 index 0000000..4502ae3 --- /dev/null +++ b/include/rendering/requirements/point_cloud_requirements.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "assets/components/point_cloud_vertex_components.hpp" +#include "shader_program/capabilities/point_cloud_capabilities.hpp" + +#include + +namespace rendering::requirements::point_cloud +{ + +struct type +{ + shader_program::capabilities::point_cloud::indices::type shader_program_requirement_index{}; + components::point_cloud_vertex::flags vertex_requirements{ + components::point_cloud_vertex::flags::none + }; +}; + + +enum class flags : int +{ + none = 0, + position = 1 << 0, + vertex_color = 1 << 1, + uniform_color = 1 << 2, + normal = 1 << 3, + reflectance = 1 << 4, + rainbow = 1 << 5 +}; + + +constexpr inline auto position = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::position, + .vertex_requirements = components::point_cloud_vertex::flags::position +}; + +constexpr inline auto rainbow = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::rainbow +}; + +constexpr inline auto vertex_color = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::vertex_color, + .vertex_requirements = components::point_cloud_vertex::flags::color +}; + +constexpr inline auto uniform_color = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::uniform_color +}; + +constexpr inline auto normal = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::normal, + .vertex_requirements = components::point_cloud_vertex::flags::normal +}; + +constexpr inline auto reflectance = type{ + .shader_program_requirement_index = shader_program::capabilities::point_cloud::indices::reflectance, + .vertex_requirements = components::point_cloud_vertex::flags::reflectance +}; + +constexpr inline auto all = std::array{ + position, vertex_color, uniform_color, normal, reflectance, rainbow +}; +} + + +[[nodiscard]] constexpr rendering::requirements::point_cloud::flags operator|( + const rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::point_cloud::flags operator&( + const rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::point_cloud::flags operator^( + const rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr rendering::requirements::point_cloud::flags operator~(const rendering::requirements::point_cloud::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr rendering::requirements::point_cloud::flags& operator|=(rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b) { + return a = a | b; +} + +constexpr rendering::requirements::point_cloud::flags& operator&=(rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b) { + return a = a & b; +} + +constexpr rendering::requirements::point_cloud::flags& operator^=(rendering::requirements::point_cloud::flags& a, const rendering::requirements::point_cloud::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + rendering::requirements::point_cloud::flags lhs, rendering::requirements::point_cloud::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + rendering::requirements::point_cloud::flags lhs, rendering::requirements::point_cloud::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + rendering::requirements::point_cloud::flags lhs, rendering::requirements::point_cloud::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + rendering::requirements::point_cloud::flags lhs, rendering::requirements::point_cloud::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/rendering/shader_program_lookups/mesh_lookup.hpp b/include/rendering/shader_program_lookups/mesh_lookup.hpp new file mode 100644 index 0000000..c583c98 --- /dev/null +++ b/include/rendering/shader_program_lookups/mesh_lookup.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "opengl/shader_program_lookup.hpp" +#include "opengl/handles/shader_program_handle.hpp" +#include "rendering/requirements/mesh_requirements.hpp" + +namespace rendering::shader_program_lookups +{ + +class mesh_lookup +{ +public: + void add( + const zgl::shader_program_handle& shader_program_handle + ); + + [[nodiscard]] std::optional find( + requirements::mesh::flags requirements + ) const; + + void print(); + +private: + zgl::shader_program_lookup m_shader_program_lookup; +}; + +} diff --git a/include/rendering/shader_program_lookups/point_cloud_lookup.hpp b/include/rendering/shader_program_lookups/point_cloud_lookup.hpp new file mode 100644 index 0000000..4fdcd30 --- /dev/null +++ b/include/rendering/shader_program_lookups/point_cloud_lookup.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "opengl/shader_program_lookup.hpp" +#include "opengl/handles/shader_program_handle.hpp" +#include "rendering/requirements/point_cloud_requirements.hpp" + +namespace rendering::shader_program_lookups +{ + +class point_cloud_lookup +{ +public: + void add( + const zgl::shader_program_handle& shader_program_handle + ); + + [[nodiscard]] std::optional find( + requirements::point_cloud::flags requirements + ) const; + +private: + zgl::shader_program_lookup m_program_lookup; +}; + +} diff --git a/include/scene/camera.hpp b/include/scene/camera.hpp new file mode 100755 index 0000000..9195948 --- /dev/null +++ b/include/scene/camera.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "scene/camera_view.hpp" + +class camera { +public: + virtual ~camera() = default; + + virtual void update( + float time_delta, + glm::vec2 mouse_pos_delta, + float mouse_wheel_delta, + camera_view& view + ) = 0; + + virtual void look_at( + const glm::vec3& origin, + const glm::vec3& target, + camera_view& view + ) = 0; +}; diff --git a/include/scene/flying_camera.hpp b/include/scene/flying_camera.hpp new file mode 100755 index 0000000..5b50b65 --- /dev/null +++ b/include/scene/flying_camera.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "scene/camera.hpp" + + + +class flying_camera : public camera { +public: + explicit flying_camera(float yaw, float pitch, float roll); + + void update( + float time_delta, + glm::vec2 mouse_pos_delta, + float mouse_wheel_delta, + camera_view& view + ) override; + + void look_at( + const glm::vec3& origin, + const glm::vec3& target, + camera_view& view + ) override; + +private: + glm::vec3 m_velocity{ 0.0f, 0.0f, 0.0f }; + glm::mat3 m_world_rotation; + glm::vec3 m_world_up; + + float m_pitch, m_yaw, m_roll; +}; diff --git a/include/scene/lighting_setup.hpp b/include/scene/lighting_setup.hpp new file mode 100644 index 0000000..9b5a9e8 --- /dev/null +++ b/include/scene/lighting_setup.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "glm/glm.hpp" + +struct lighting_setup +{ + glm::vec3 point_light_direction; + glm::vec3 point_light_color; + glm::vec3 ambient_light_color; +}; diff --git a/include/shader_program/attributes/mesh_attributes.hpp b/include/shader_program/attributes/mesh_attributes.hpp new file mode 100644 index 0000000..c017a3d --- /dev/null +++ b/include/shader_program/attributes/mesh_attributes.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "opengl/shader_program_variable.hpp" +#include + +namespace shader_program::attributes::mesh +{ + +enum class flags : int { + none = 0, + position = 1 << 0, + normal = 1 << 1, + tex_coord = 1 << 2 +}; + +constexpr inline auto position = zgl::shader_program_variable({ GL_FLOAT_VEC3, 0 }, "vertex_position"); +constexpr inline auto normal = zgl::shader_program_variable({ GL_FLOAT_VEC3, 1 }, "vertex_normal"); +constexpr inline auto tex_coord = zgl::shader_program_variable({ GL_FLOAT_VEC2, 2 }, "vertex_tex_coord"); + +constexpr inline auto all = std::array{ + position, normal, tex_coord +}; + +} + + +[[nodiscard]] constexpr shader_program::attributes::mesh::flags operator|( + const shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::mesh::flags operator&( + const shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::mesh::flags operator^( + const shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::mesh::flags operator~(const shader_program::attributes::mesh::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr shader_program::attributes::mesh::flags& operator|=(shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b) { + return a = a | b; +} + +constexpr shader_program::attributes::mesh::flags& operator&=(shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b) { + return a = a & b; +} + +constexpr shader_program::attributes::mesh::flags& operator^=(shader_program::attributes::mesh::flags& a, const shader_program::attributes::mesh::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + shader_program::attributes::mesh::flags lhs, shader_program::attributes::mesh::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + shader_program::attributes::mesh::flags lhs, shader_program::attributes::mesh::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + shader_program::attributes::mesh::flags lhs, shader_program::attributes::mesh::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + shader_program::attributes::mesh::flags lhs, shader_program::attributes::mesh::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/shader_program/attributes/point_cloud_attributes.hpp b/include/shader_program/attributes/point_cloud_attributes.hpp new file mode 100644 index 0000000..5533e34 --- /dev/null +++ b/include/shader_program/attributes/point_cloud_attributes.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "opengl/shader_program_variable.hpp" +#include + +namespace shader_program::attributes::point_cloud +{ + +enum class flags : int { + none = 0, + position = 1 << 0, + normal = 1 << 1, + color = 1 << 2, + reflectance = 1 << 3 +}; + +constexpr inline auto position = zgl::shader_program_variable({ GL_FLOAT_VEC3, 0 }, "vertex_position"); +constexpr inline auto normal = zgl::shader_program_variable({ GL_FLOAT_VEC3, 1 }, "vertex_normal"); +constexpr inline auto color = zgl::shader_program_variable({ GL_FLOAT_VEC3, 2 }, "vertex_color"); +constexpr inline auto reflectance = zgl::shader_program_variable({ GL_FLOAT, 2 }, "vertex_reflectance"); + +constexpr inline auto all = std::array{ + position, normal, color, reflectance +}; + +} + +[[nodiscard]] constexpr shader_program::attributes::point_cloud::flags operator|( + const shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::point_cloud::flags operator&( + const shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::point_cloud::flags operator^( + const shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::attributes::point_cloud::flags operator~(const shader_program::attributes::point_cloud::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr shader_program::attributes::point_cloud::flags& operator|=(shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b) { + return a = a | b; +} + +constexpr shader_program::attributes::point_cloud::flags& operator&=(shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b) { + return a = a & b; +} + +constexpr shader_program::attributes::point_cloud::flags& operator^=(shader_program::attributes::point_cloud::flags& a, const shader_program::attributes::point_cloud::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + shader_program::attributes::point_cloud::flags lhs, shader_program::attributes::point_cloud::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + shader_program::attributes::point_cloud::flags lhs, shader_program::attributes::point_cloud::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + shader_program::attributes::point_cloud::flags lhs, shader_program::attributes::point_cloud::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + shader_program::attributes::point_cloud::flags lhs, shader_program::attributes::point_cloud::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/shader_program/capabilities/mesh_capabilities.hpp b/include/shader_program/capabilities/mesh_capabilities.hpp new file mode 100644 index 0000000..fd6b1f1 --- /dev/null +++ b/include/shader_program/capabilities/mesh_capabilities.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/components/point_cloud_vertex_components.hpp" +#include "assets/components/material_components.hpp" +#include "shader_program/attributes/mesh_attributes.hpp" +#include "shader_program/uniforms/mesh_uniforms.hpp" + +namespace shader_program::capabilities::mesh +{ + +struct type +{ + attributes::mesh::flags attributes{ + attributes::mesh::flags::none + }; + uniforms::mesh::flags uniforms{ + uniforms::mesh::flags::none + }; +}; + +namespace indices +{ +using type = ztu::u8; +constexpr inline type position = 0; +constexpr inline type lit = 1; +constexpr inline type textured = 2; +constexpr inline type uniform_color = 3; +constexpr inline type uniform_alpha = 4; +constexpr inline type point = 5; +} + +enum class flags : int +{ + none = 0, + position = 1 << indices::position, + lit = 1 << indices::lit, + textured = 1 << indices::textured, + uniform_color = 1 << indices::uniform_color, + uniform_alpha = 1 << indices::uniform_alpha, + point = 1 << indices::point +}; + +constexpr inline auto position = type{ + .attributes = attributes::mesh::flags::position, + .uniforms = uniforms::mesh::flags::mvp +}; + +constexpr inline auto lit = type{ + .attributes = attributes::mesh::flags::normal, + .uniforms = ( + uniforms::mesh::flags::model_matrix | + uniforms::mesh::flags::view_pos | + uniforms::mesh::flags::point_light_direction | + uniforms::mesh::flags::point_light_color | + uniforms::mesh::flags::ambient_light_color | + uniforms::mesh::flags::ambient_filter | + uniforms::mesh::flags::diffuse_filter | + uniforms::mesh::flags::specular_filter | + uniforms::mesh::flags::shininess + ) +}; + +constexpr inline auto point = type{ + .uniforms = uniforms::mesh::flags::point_size +}; + +constexpr inline auto textured = type{ + .attributes = attributes::mesh::flags::tex_coord, + .uniforms = uniforms::mesh::flags::tex +}; + +constexpr inline auto uniform_color = type{ + .uniforms = uniforms::mesh::flags::color +}; + +constexpr inline auto uniform_alpha = type{ + .uniforms = uniforms::mesh::flags::alpha +}; + +constexpr inline auto all = std::array{ + position, lit, textured, uniform_color, uniform_alpha, point +}; + +} diff --git a/include/shader_program/capabilities/point_cloud_capabilities.hpp b/include/shader_program/capabilities/point_cloud_capabilities.hpp new file mode 100644 index 0000000..018a09a --- /dev/null +++ b/include/shader_program/capabilities/point_cloud_capabilities.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "shader_program/attributes/point_cloud_attributes.hpp" +#include "shader_program/uniforms/point_cloud_uniforms.hpp" + +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/components/point_cloud_vertex_components.hpp" + +#include + +namespace shader_program::capabilities::point_cloud +{ + +struct type +{ + attributes::point_cloud::flags attributes{ + attributes::point_cloud::flags::none + }; + uniforms::point_cloud::flags uniforms{ + uniforms::point_cloud::flags::none + }; +}; + +namespace indices +{ +using type = ztu::u8; +constexpr inline type position = 0; +constexpr inline type vertex_color = 1; +constexpr inline type uniform_color = 2; +constexpr inline type normal = 3; +constexpr inline type reflectance = 4; +constexpr inline type rainbow = 5; +} + +enum class flags : int +{ + none = 0, + position = 1 << indices::position, + vertex_color = 1 << indices::vertex_color, + uniform_color = 1 << indices::uniform_color, + normal = 1 << indices::normal, + reflectance = 1 << indices::reflectance, + rainbow = 1 << indices::rainbow + }; + +constexpr inline auto position = type{ + .attributes = attributes::point_cloud::flags::position, + .uniforms = uniforms::point_cloud::flags::mvp +}; + +constexpr inline auto rainbow = type{}; + +constexpr inline auto vertex_color = type{ + .attributes = attributes::point_cloud::flags::color +}; + +constexpr inline auto uniform_color = type{ + .uniforms = uniforms::point_cloud::flags::color +}; + +constexpr inline auto normal = type{ + .attributes = attributes::point_cloud::flags::normal, + .uniforms = ( + uniforms::point_cloud::flags::model | + uniforms::point_cloud::flags::camera_position + ) +}; + +constexpr inline auto reflectance = type{ + .attributes = attributes::point_cloud::flags::reflectance + }; + +constexpr inline auto all = std::array{ + position, vertex_color, uniform_color, normal, reflectance, rainbow +}; + +} diff --git a/include/shader_program/uniforms/mesh_uniforms.hpp b/include/shader_program/uniforms/mesh_uniforms.hpp new file mode 100644 index 0000000..afa8dd8 --- /dev/null +++ b/include/shader_program/uniforms/mesh_uniforms.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include "opengl/shader_program_variable.hpp" +#include + +namespace shader_program::uniforms::mesh +{ + +enum class flags : int +{ + none = 0, + mvp = 1 << 0, + model_matrix = 1 << 1, + point_size = 1 << 2, + color = 1 << 3, + tex = 1 << 4, + view_pos = 1 << 5, + point_light_direction = 1 << 6, + point_light_color = 1 << 7, + ambient_light_color = 1 << 8, + ambient_filter = 1 << 9, + diffuse_filter = 1 << 10, + specular_filter = 1 << 11, + shininess = 1 << 12, + alpha = 1 << 13 +}; + +constexpr inline auto mvp = zgl::shader_program_variable({ GL_FLOAT_MAT4, 0 }, "mvp_matrix"); +constexpr inline auto model_matrix = zgl::shader_program_variable({ GL_FLOAT_MAT4, 1 }, "model_matrix"); +constexpr inline auto point_size = zgl::shader_program_variable({ GL_FLOAT, 2 }, "point_size"); +constexpr inline auto color = zgl::shader_program_variable({ GL_FLOAT_VEC4, 3 }, "color"); +constexpr inline auto tex = zgl::shader_program_variable({ GL_SAMPLER_2D, 3 }, "tex"); +constexpr inline auto view_pos = zgl::shader_program_variable({ GL_FLOAT_VEC3, 4 }, "view_pos"); +constexpr inline auto point_light_direction = zgl::shader_program_variable({ GL_FLOAT_VEC3, 5 }, "point_light_direction"); +constexpr inline auto point_light_color = zgl::shader_program_variable({ GL_FLOAT_VEC3, 6 }, "point_light_color"); +constexpr inline auto ambient_light_color = zgl::shader_program_variable({ GL_FLOAT_VEC3, 7 }, "ambient_light_color"); +constexpr inline auto ambient_filter = zgl::shader_program_variable({ GL_FLOAT_VEC3, 8 }, "ambient_filter"); +constexpr inline auto diffuse_filter = zgl::shader_program_variable({ GL_FLOAT_VEC3, 9 }, "diffuse_filter"); +constexpr inline auto specular_filter = zgl::shader_program_variable({ GL_FLOAT_VEC3, 10 }, "specular_filter"); +constexpr inline auto shininess = zgl::shader_program_variable({ GL_FLOAT, 11 }, "shininess"); +constexpr inline auto alpha = zgl::shader_program_variable({ GL_FLOAT, 12 }, "alpha"); + +constexpr inline auto all = std::array{ + mvp, + model_matrix, + point_size, + color, + tex, + view_pos, + point_light_direction, + point_light_color, + ambient_light_color, + ambient_filter, + diffuse_filter, + specular_filter, + shininess, + alpha +}; +} + +[[nodiscard]] constexpr shader_program::uniforms::mesh::flags operator|( + const shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::mesh::flags operator&( + const shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::mesh::flags operator^( + const shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::mesh::flags operator~(const shader_program::uniforms::mesh::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr shader_program::uniforms::mesh::flags& operator|=(shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b) { + return a = a | b; +} + +constexpr shader_program::uniforms::mesh::flags& operator&=(shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b) { + return a = a & b; +} + +constexpr shader_program::uniforms::mesh::flags& operator^=(shader_program::uniforms::mesh::flags& a, const shader_program::uniforms::mesh::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + shader_program::uniforms::mesh::flags lhs, shader_program::uniforms::mesh::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + shader_program::uniforms::mesh::flags lhs, shader_program::uniforms::mesh::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + shader_program::uniforms::mesh::flags lhs, shader_program::uniforms::mesh::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + shader_program::uniforms::mesh::flags lhs, shader_program::uniforms::mesh::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/shader_program/uniforms/point_cloud_uniforms.hpp b/include/shader_program/uniforms/point_cloud_uniforms.hpp new file mode 100644 index 0000000..f849ab2 --- /dev/null +++ b/include/shader_program/uniforms/point_cloud_uniforms.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "opengl/shader_program_variable.hpp" +#include + +namespace shader_program::uniforms::point_cloud +{ + +enum class flags : int +{ + none = 0, + mvp = 1 << 0, + point_size = 1 << 1, + color = 1 << 2, + model = 1 << 3, + camera_position = 1 << 4, + rainbow_offset_y = 1 << 5, + rainbow_scale_y = 1 << 6, +}; + +constexpr inline auto mvp = zgl::shader_program_variable({ GL_FLOAT_MAT4, 0 }, "mvp"); +constexpr inline auto point_size = zgl::shader_program_variable({ GL_FLOAT, 2 }, "point_size"); +constexpr inline auto color = zgl::shader_program_variable({ GL_FLOAT_VEC4, 3 }, "color"); +constexpr inline auto model = zgl::shader_program_variable({ GL_FLOAT_MAT4, 4 }, "model"); +constexpr inline auto camera_position = zgl::shader_program_variable({ GL_FLOAT_VEC3, 5 }, "camera_position"); +constexpr inline auto rainbow_offset_y = zgl::shader_program_variable({ GL_FLOAT, 6 }, "rainbow_offset_y"); +constexpr inline auto rainbow_scale_y = zgl::shader_program_variable({ GL_FLOAT, 7 }, "rainbow_scale_y"); + +constexpr inline auto all = std::array{ + mvp, + point_size, + color, + model, + camera_position, + rainbow_offset_y, + rainbow_scale_y +}; + +} + +[[nodiscard]] constexpr shader_program::uniforms::point_cloud::flags operator|( + const shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b +) { + return static_cast(static_cast(a) | static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::point_cloud::flags operator&( + const shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b +) { + return static_cast(static_cast(a) & static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::point_cloud::flags operator^( + const shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b +) { + return static_cast(static_cast(a) ^ static_cast(b)); +} + +[[nodiscard]] constexpr shader_program::uniforms::point_cloud::flags operator~(const shader_program::uniforms::point_cloud::flags& a) { + return static_cast(~static_cast(a)); +} + +constexpr shader_program::uniforms::point_cloud::flags& operator|=(shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b) { + return a = a | b; +} + +constexpr shader_program::uniforms::point_cloud::flags& operator&=(shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b) { + return a = a & b; +} + +constexpr shader_program::uniforms::point_cloud::flags& operator^=(shader_program::uniforms::point_cloud::flags& a, const shader_program::uniforms::point_cloud::flags& b) { + return a = a ^ b; +} + +[[nodiscard]] constexpr bool operator<( + shader_program::uniforms::point_cloud::flags lhs, shader_program::uniforms::point_cloud::flags rhs +) { + return static_cast(lhs) < static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator<=( + shader_program::uniforms::point_cloud::flags lhs, shader_program::uniforms::point_cloud::flags rhs +) { + return static_cast(lhs) <= static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>( + shader_program::uniforms::point_cloud::flags lhs, shader_program::uniforms::point_cloud::flags rhs +) { + return static_cast(lhs) > static_cast(rhs); +} + +[[nodiscard]] constexpr bool operator>=( + shader_program::uniforms::point_cloud::flags lhs, shader_program::uniforms::point_cloud::flags rhs +) { + return static_cast(lhs) >= static_cast(rhs); +} diff --git a/include/util/arx.hpp b/include/util/arx.hpp new file mode 100755 index 0000000..fced69c --- /dev/null +++ b/include/util/arx.hpp @@ -0,0 +1,285 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "util/uix.hpp" +#include "util/pack.hpp" +#include "util/function.hpp" +#include "util/string_literal.hpp" +#include "util/for_each.hpp" + + +namespace ztu { + +static constexpr char NO_SHORT_FLAG = '\0'; + +namespace arx_internal { + +template +concept parsing_function = ( + ztu::callable, std::string_view> and + (not std::is_class_v or std::is_empty_v) +); + +} // namespace arx_internal + +template< + char ShortName, + ztu::string_literal LongName, + typename T = bool, + auto Parse = std::nullptr_t{} +> requires ( +std::same_as or + arx_internal::parsing_function +) +struct arx_flag { + static constexpr auto short_name = ShortName; + static constexpr auto long_name = LongName; + using type = T; + static constexpr auto parse = Parse; +}; + +namespace arx_parsers { + +template +requires (Base > 0) +[[nodiscard]] inline std::optional integer(const std::string_view& str); + +template +[[nodiscard]] inline std::optional floating_point(const std::string_view& str); + +} // namespace arx_parsers + +namespace arx_internal { + +template +struct flag_by_name { + template + struct pred : std::conditional_t< + Flag::long_name == LongName, + std::true_type, + std::false_type + > { + }; + using type = ztu::find; +}; + +template +using flag_by_name_t = flag_by_name::type; + +template +using flag_type_by_name_t = flag_by_name_t::type; + +} // namespace arx_internal + + +template +class arx { +private: + static constexpr auto short_flag_prefix = std::string_view{ "-" }; + static constexpr auto long_flag_prefix = std::string_view{ "--" }; + static constexpr auto UNBOUND_ARGUMENT = ztu::isize_max; + +public: + inline arx(int num_args, const char* const* args); + + template + [[nodiscard]] inline std::optional> get() const; + + [[nodiscard]] inline std::optional get(ztu::isize position) const; + + [[nodiscard]] inline ztu::isize num_positional() const; + +protected: + [[nodiscard]] inline std::optional find_flag_value(ztu::isize flag_index) const; + + template + [[nodiscard]] inline std::optional> + parse_value(const std::string_view& value_str) const; + +private: + std::vector> m_arguments; + ztu::isize m_unbound_begin; +}; + + +namespace arx_parsers { + +template +requires (Base > 0) +[[nodiscard]] inline std::optional integer(const std::string_view& str) { + Type value{}; + const auto [ptr, ec] = std::from_chars(str.begin(), str.end(), value, Base); + if (ec == std::errc() and ptr == str.end()) { + return value; + } + return std::nullopt; +} + +template +[[nodiscard]] inline std::optional floating_point(const std::string_view& str) { + Type value{}; + const auto [ptr, ec] = std::from_chars(str.begin(), str.end(), value, Format); + if (ec == std::errc() and ptr == str.end()) { + return value; + } + return std::nullopt; +} + +} // namespace arx_parsers + +template +arx::arx(int num_args, const char* const* args) { + + m_arguments.reserve(std::max(num_args - 1, 0)); + + for (int i = 1; i < num_args; i++) { + const auto argument = std::string_view{ args[i] }; + + const auto found_match = for_each::indexed_type( + [&]() { + if (( + Flag::short_name != NO_SHORT_FLAG and + argument.length() == short_flag_prefix.length() + 1 and + argument.starts_with(short_flag_prefix) and + argument[short_flag_prefix.length()] == Flag::short_name + ) or ( + argument.length() == long_flag_prefix.length() + Flag::long_name.length() and + argument.starts_with(long_flag_prefix) and + argument.substr(long_flag_prefix.length()) == Flag::long_name + )) { + if constexpr (std::same_as) { + m_arguments.emplace_back(Index, argument); + } else { + if (i + 1 < num_args) { + const auto value = std::string_view{ args[++i] }; + m_arguments.emplace_back(Index, value); + } + } + return true; + } + return false; + } + ); + + if (not found_match) { + m_arguments.emplace_back(UNBOUND_ARGUMENT, argument); + } + } + + std::sort( + m_arguments.begin(), m_arguments.end(), [](const auto& a, const auto& b) -> bool { + return a.first < b.first; + } + ); + + const auto first_unbound = std::find_if( + m_arguments.begin(), m_arguments.end(), [](const auto& a) { + return a.first == UNBOUND_ARGUMENT; + } + ); + + const auto num_bound = m_arguments.end() - first_unbound; + + const auto last = std::unique( + std::reverse_iterator(first_unbound), m_arguments.rend(), [](const auto& a, const auto& b) { + return a.first == b.first; + } + ).base(); + + m_arguments.erase(m_arguments.begin(), last); + + m_unbound_begin = m_arguments.size() - num_bound; +}; + + +template +std::optional arx::find_flag_value(isize flag_index) const { + const auto begin = m_arguments.begin(); + const auto end = begin + m_unbound_begin; + const auto it = std::lower_bound( + begin, end, flag_index, [](const auto& a, const auto& b) { + return a.first < b; + } + ); + if (it != end and it->first == flag_index) { + return { it->second }; + } + return std::nullopt; +} + + +template +template +std::optional> arx::parse_value( + const std::string_view& value_str +) const { + + using Flag = arx_internal::flag_by_name_t; + using Type = Flag::type; + using opt_t = std::optional; + + opt_t ret; + + // Use custom parser if provided and if not try using a default parser + if constexpr ( + requires(const std::string_view str) { + { + std::invoke(Flag::parse, str) + } -> std::same_as; + } + ) { + return std::invoke(Flag::parse, value_str); + } else if constexpr (std::integral && not std::same_as) { + return arx_parsers::integer(value_str); + } else if constexpr (std::floating_point) { + return arx_parsers::floating_point(value_str); + } else if constexpr (std::same_as) { + return value_str; + } else if constexpr (std::same_as) { + return std::string(value_str); + } else { + Type::__cannot_parse_this_type; + } + + return ret; +} + + +template +template +std::optional> arx::get() const { + using Flag = arx_internal::flag_by_name_t; + static constexpr auto index = index_of; + + if (index < sizeof...(Flags)) { + const auto value_opt = find_flag_value(index); + if constexpr (std::same_as) { + return value_opt.has_value(); + } else if (value_opt) { + return parse_value(*value_opt); + } + } + + return std::nullopt; +} + +template +inline isize arx::num_positional() const { + return m_arguments.size() - m_unbound_begin; +} + +template +std::optional arx::get(isize position) const { + if (0 <= position and position < num_positional()) { + return { m_arguments[m_unbound_begin + position].second }; + } + return std::nullopt; +} + +} // namespace ztu + diff --git a/include/util/binary_ifstream.hpp b/include/util/binary_ifstream.hpp new file mode 100644 index 0000000..14dd1a1 --- /dev/null +++ b/include/util/binary_ifstream.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include +#include +#include +#include +#include + +class binary_ifstream { +public: + using char_type = char; + using size_type = std::streamsize; + + [[nodiscard]] inline std::error_code open( + const std::filesystem::path& filename, + bool check_file_before_open + ); + + [[nodiscard]] inline std::error_code read(std::span buffer); + + template + [[nodiscard]] std::error_code read(T& integer); + + template + [[nodiscard]] std::error_code read_ieee754(T& floating_point); + + [[nodiscard]] inline std::error_code skip(size_type count); + + template + [[nodiscard]] std::error_code skip(); + + template + [[nodiscard]] std::error_code skip(); + + inline void close(); + +protected: + [[nodiscard]] inline static std::error_code check_file( + const std::filesystem::path& filename + ); + + [[nodiscard]] inline std::errc get_stream_error_code() const; + +private: + std::basic_ifstream in{}; +}; + +inline std::errc binary_ifstream::get_stream_error_code() const { + const auto state = in.rdstate(); + + auto code = std::errc{}; + + if (state != std::ios::goodbit) + { + code = std::errc::state_not_recoverable; + + if (state & std::ios::failbit) // irrecoverable stream error + { + code = std::errc::state_not_recoverable; + } + else if (state & std::ios::badbit) // input/output operation failed + { + code = std::errc::io_error; + } + else if (state & std::ios::eofbit) // input sequence has reached end-of-file + { + code = std::errc::result_out_of_range; + } + } + + return code; +} + +inline std::error_code binary_ifstream::check_file(const std::filesystem::path& filename) +{ + auto error = std::error_code{}; + + const auto is_file = std::filesystem::exists(filename, error); + + if (error) + { + return error; + } + + if (not is_file) + { + return std::make_error_code(std::errc::invalid_argument); + } + + const auto files_exists = std::filesystem::exists(filename, error); + + if (error) + { + return error; + } + + if (not files_exists) + { + return std::make_error_code(std::errc::no_such_file_or_directory); + } + + return {}; +} + +inline std::error_code binary_ifstream::open( + const std::filesystem::path& filename, + bool check_file_before_open +) { + if (check_file_before_open) + { + if (const auto error = check_file(filename)) + { + return error; + } + } + + in = {}; + + // Turn of exceptions, as errors are handled via the stream state. + in.exceptions(std::ios::goodbit); + + in.open(filename, std::ios::binary | std::ios::in); + + if (const auto error = std::make_error_code(get_stream_error_code())) + { + return error; + } + + if (not in.is_open()) + { + // Unknown error, so 'state_not_recoverable' is assumed. + return std::make_error_code(std::errc::state_not_recoverable); + } + + return {}; +} + +inline void binary_ifstream::close() +{ + in.close(); +} + +inline std::error_code binary_ifstream::skip(size_type count) +{ + in.ignore(count); + return std::make_error_code(get_stream_error_code()); +} + +template +std::error_code binary_ifstream::skip() +{ + return skip(sizeof(T)); +} + +template +std::error_code binary_ifstream::skip() +{ + return skip(sizeof(T)); +} + +inline std::error_code binary_ifstream::read(std::span buffer) +{ + in.read(buffer.data(), static_cast(buffer.size())); + return std::make_error_code(get_stream_error_code()); +} + +template +std::error_code binary_ifstream::read(T& integer) +{ + std::array buffer; + if (const auto error = read(buffer)) + { + return error; + } + + if constexpr (std::endian::native != E) + { + std::reverse(buffer.begin(), buffer.end()); + } + + // Use memcpy to avoid UB. + std::memcpy(static_cast(&integer), buffer.data(), buffer.size()); + + return {}; +} + +template +std::error_code binary_ifstream::read_ieee754(T& floating_point) +{ + static_assert(std::numeric_limits::is_iec559); + + std::array buffer; + if (const auto error = read(buffer)) + { + return error; + } + + if constexpr (std::endian::native != E) + { + std::reverse(buffer.begin(), buffer.end()); + } + + // Use memcpy to avoid UB. + std::memcpy(static_cast(&floating_point), buffer.data(), buffer.size()); + + return {}; +} diff --git a/include/util/enum_operators.hpp b/include/util/enum_operators.hpp new file mode 100644 index 0000000..601df2c --- /dev/null +++ b/include/util/enum_operators.hpp @@ -0,0 +1,57 @@ +#pragma once + +#define DEFINE_ENUM_FLAG_OPERATORS(ENUM_TYPE) \ +[[nodiscard]] constexpr ENUM_TYPE operator|( \ + const ENUM_TYPE lhs, const ENUM_TYPE rhs \ +) { \ + return static_cast( \ + static_cast>(lhs) | \ + static_cast>(rhs) \ + ); \ +} \ + \ +[[nodiscard]] constexpr ENUM_TYPE operator&( \ + const ENUM_TYPE lhs, const ENUM_TYPE rhs \ +) { \ + return static_cast( \ + static_cast>(lhs) & \ + static_cast>(rhs) \ + ); \ +} \ + \ +[[nodiscard]] constexpr ENUM_TYPE operator^( \ + const ENUM_TYPE lhs, const ENUM_TYPE rhs \ +) { \ + return static_cast( \ + static_cast>(lhs) ^ \ + static_cast>(rhs) \ + ); \ +} \ + \ +[[nodiscard]] constexpr ENUM_TYPE operator~(const ENUM_TYPE a) \ +{ \ + return static_cast( \ + ~static_cast>(a) \ + ); \ +} \ + \ +constexpr ENUM_TYPE& operator|=( \ + ENUM_TYPE& lhs, \ + const ENUM_TYPE rhs \ +) { \ + return lhs = lhs | rhs; \ +} \ + \ +constexpr ENUM_TYPE& operator&=( \ + ENUM_TYPE& lhs, \ + const ENUM_TYPE rhs \ +) { \ + return lhs = lhs & rhs; \ +} \ + \ +constexpr ENUM_TYPE& operator^=( \ + ENUM_TYPE& lhs, \ + const ENUM_TYPE rhs \ +) { \ + return lhs = lhs ^ rhs; \ +} diff --git a/include/util/extra_arx_parsers.hpp b/include/util/extra_arx_parsers.hpp new file mode 100755 index 0000000..41c8d2d --- /dev/null +++ b/include/util/extra_arx_parsers.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra_arx_parsers { + + template + requires (Count > 0) + [[nodiscard]] inline std::optional> glm_vec(const std::string_view& str) { + glm::vec vec{}; + auto it = str.cbegin(); + for (int i = 0; i < Count; i++) { + const auto [ptr, ec] = std::from_chars(it, str.cend(), vec[i], std::chars_format::general); + if (ec != std::errc()) { + return std::nullopt; + } + it = ptr + 1; // skip space in between components + } + if (it < str.cend()) { + return std::nullopt; + } + return vec; + } + +} diff --git a/include/util/for_each.hpp b/include/util/for_each.hpp new file mode 100755 index 0000000..75ddd17 --- /dev/null +++ b/include/util/for_each.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace ztu::for_each { + + template + inline constexpr bool type(auto &&f) { + return (f.template operator()() || ...); + } + + template + inline constexpr bool value(auto &&f) { + return (f.template operator()() || ...); + } + + template + inline constexpr bool argument(auto &&f, Args&&... args) { + return (f(std::forward(args)) || ...); + } + + template + inline constexpr bool index(auto&& f) { + return [&](std::index_sequence) { + return (f.template operator()() || ...); + }(std::make_index_sequence()); + } + + template + inline constexpr bool indexed_type(auto &&f) { + return [&](std::index_sequence) { + return (f.template operator()() || ...); + }(std::make_index_sequence()); + } + + template + inline constexpr bool indexed_value(auto &&f) { + return [&](std::index_sequence) { + return (f.template operator()() || ...); + }(std::make_index_sequence()); + } + + template + inline constexpr bool indexed_argument(auto &&f, Args&&... args) { + return [&](std::index_sequence) { + return (f.template operator()(std::forward(args)) || ...); + }(std::make_index_sequence()); + } +} diff --git a/include/util/function.hpp b/include/util/function.hpp new file mode 100755 index 0000000..cc4b80d --- /dev/null +++ b/include/util/function.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +namespace ztu { + + template + concept callable = std::same_as, R>; + + template + concept runnable = callable; + + template + concept supplier = callable; + + template + concept consumer = callable; + + template + concept predicate = callable; + + + template + struct function_meta; + + template + struct function_meta { + using ret_t = R; + using args_t = std::tuple; + static constexpr bool is_const = false; + }; + + template + struct function_meta { + using class_t = C; + using ret_t = R; + using args_t = std::tuple; + static constexpr bool is_const = false; + }; + + template + struct function_meta { + using class_t = C; + using ret_t = R; + using args_t = std::tuple; + static constexpr bool is_const = true; + }; + + namespace function { + template + using class_t = typename function_meta::class_t; + + template + using ret_t = typename function_meta::ret_t; + + template + using args_t = typename function_meta::args_t; + + template + constexpr bool is_const_v = function_meta::is_const; + } +} diff --git a/include/util/id_type.hpp b/include/util/id_type.hpp new file mode 100644 index 0000000..ac91005 --- /dev/null +++ b/include/util/id_type.hpp @@ -0,0 +1,25 @@ +#pragma once + +namespace ztu +{ +template +class id_type_for +{ + friend Parent; + + using index_type = IndexType; + + explicit constexpr id_type_for(index_type index) : index{ index } {} + + index_type index{}; + +public: + constexpr id_type_for() = default; + constexpr auto operator<=>(const id_type_for&) const = default; + + constexpr operator bool() const + { + return index == index_type{}; + } +}; +} \ No newline at end of file diff --git a/include/util/image.hpp b/include/util/image.hpp new file mode 100755 index 0000000..a56d736 --- /dev/null +++ b/include/util/image.hpp @@ -0,0 +1,327 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include + + +#if defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC system_header +#elif defined(_MSC_VER) +#pragma warning(push, 0) +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" + + +#define STBI_MSC_SECURE_CRT +#define STB_IMAGE_WRITE_STATIC +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + + +#if defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +namespace ztu { + +template +class image { +public: + using value_type = C; + using size_type = std::make_signed_t; + using difference_type = size_type; + using reference = std::add_lvalue_reference_t; + using const_reference = std::add_const_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = pointer; + using const_iterator = const_pointer; + +public: + [[nodiscard]] static std::error_code load(const char* filename, image& dst, bool flip = false); + + [[nodiscard]] static image create(size_type width, size_type height, const value_type& color); + +public: + image() = default; + + image(std::unique_ptr&& data, size_type width, size_type height); + + image(const image&); + + image(image&&) noexcept; + + [[nodiscard]] image& operator=(const image&); + + [[nodiscard]] image& operator=(image&&) noexcept; + + [[nodiscard]] value_type operator()(double x, double y) const; + + [[nodiscard]] const_reference operator()(size_type x, size_type y) const; + + [[nodiscard]] reference operator()(size_type x, size_type y); + + [[nodiscard]] const_iterator operator[](size_type y) const; + + [[nodiscard]] iterator operator[](size_type y); + + [[nodiscard]] int save(const std::string& filename) const; + + [[nodiscard]] bool contains(size_type x, size_type y) const; + + [[nodiscard]] size_type width() const; + + [[nodiscard]] size_type height() const; + + [[nodiscard]] std::pair size() const; + + [[nodiscard]] size_type num_pixels() const; + + [[nodiscard]] const_iterator begin() const; + + [[nodiscard]] iterator begin(); + + [[nodiscard]] const_iterator end() const; + + [[nodiscard]] iterator end(); + + [[nodiscard]] const_iterator cbegin() const; + + [[nodiscard]] const_iterator cend() const; + + [[nodiscard]] const_pointer data() const; + + [[nodiscard]] pointer data(); + +private: + std::unique_ptr m_data{ nullptr }; + size_type m_width{ 0 }, m_height{ 0 }; +}; + +template +image::image(std::unique_ptr&& data, const size_type width, const size_type height) : + m_data{ std::move(data) }, m_width{ width }, m_height{ height } { +}; + +template +image::image(const image& other) : + m_data{ new C[other.m_width * other.m_height] }, + m_width{ other.m_width }, m_height{ other.m_height } { + std::copy_n(other.m_data.get(), other.m_width * other.m_height, this->m_data.get()); +} + +template +image::image(image&& other) noexcept : + m_data{ std::move(other.m_data) }, + m_width{ other.m_width }, m_height{ other.m_height } { + other.m_width = 0; + other.m_height = 0; +} + +template +image& image::operator=(const image& other) { + if (this != &other) { + + const auto m_num_pixels = m_width * m_height; + const auto o_num_pixels = other.m_width * other.m_height; + + if (o_num_pixels > m_num_pixels) { + this->~image(); + this->m_data = new C[o_num_pixels]; + } + + std::copy_n(other.m_data, o_num_pixels, this->m_data); + + this->m_width = other.m_width; + this->m_height = other.m_height; + } + return *this; +} + +template +image& image::operator=(image&& other) noexcept { + if (this != &other) { + this->~image(); + + this->m_data = std::move(other.m_data); + this->m_width = other.m_width; + this->m_height = other.m_height; + + other.m_width = 0; + other.m_height = 0; + } + return *this; +} + + +template +std::error_code image::load(const char* filename, image& dst, bool flip) { + int width, height, channels; + + stbi_set_flip_vertically_on_load(flip); + + auto data = reinterpret_cast(stbi_load(filename, &width, &height, &channels, sizeof(C))); + + if (data == nullptr) { + return std::make_error_code(std::errc::no_such_file_or_directory); + } + + dst.m_data.reset(data); + dst.m_width = static_cast(width); + dst.m_height = static_cast(height); + + return {}; +} + +template +image image::create(const size_type width, const size_type height, const C& color) { + + const auto num_pixels = width * height; + C* data = new C[num_pixels]; + + std::fill_n(data, num_pixels, color); + + return image(data, width, height); +} + +template +typename image::size_type image::width() const { + return m_width; +} + +template +typename image::size_type image::height() const { + return m_height; +} + +template +std::pair::size_type, typename image::size_type> image::size() const { + return { m_width, m_height }; +} + +template +typename image::size_type image::num_pixels() const { + return m_width * m_height; +} + +template +int image::save(const std::string& filename) const { + + std::string ext = filename.substr(filename.rfind('.') + 1, filename.length()); + std::transform( + ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { + return std::tolower(c); + } + ); + + int status = -1; + + if (ext == "png") { + status = stbi_write_png(filename.c_str(), m_width, m_height, sizeof(C), m_data, m_width * sizeof(C)); + } else if (ext == "bmp") { + status = stbi_write_bmp(filename.c_str(), m_width, m_height, sizeof(C), m_data); + } else if (ext == "tga") { + status = stbi_write_tga(filename.c_str(), m_width, m_height, sizeof(C), m_data); + } else if (ext == "jpg" || ext == "jpeg") { + status = stbi_write_jpg(filename.c_str(), m_width, m_height, sizeof(C), m_data, m_width * sizeof(C)); + } + + return status; +} + +template +bool image::contains(size_type x, size_type y) const { + return 0 <= x and x < m_width and 0 <= y and y < m_height; +} + +template +typename image::const_reference image::operator()(size_type x, size_type y) const { + const auto clamped_x = std::clamp(x, static_cast(0), m_width - 1); + const auto clamped_y = std::clamp(y, static_cast(0), m_height - 1); + return m_data[clamped_x + clamped_y * m_width]; +} + +template +typename image::reference image::operator()(size_type x, size_type y) { + const auto clamped_x = std::clamp(x, static_cast(0), m_width - 1); + const auto clamped_y = std::clamp(y, static_cast(0), m_height - 1); + return m_data[clamped_x + clamped_y * m_width]; +} + +template +typename image::value_type image::operator()(double x, double y) const { + auto min_x = static_cast(std::floor(x)); + auto min_y = static_cast(std::floor(y)); + const auto px00 = (*this)(min_x, min_y), px10 = (*this)(min_x + 1, min_y); + const auto px01 = (*this)(min_x, min_y + 1), px11 = (*this)(min_x + 1, min_y + 1); + const auto a_x = x - static_cast(min_x), a_y = y - static_cast(min_y); + return std::lerp( + std::lerp(px00, px10, a_x), + std::lerp(px01, px11, a_x), + a_y + ); +} + +template +typename image::const_iterator image::operator[](size_type y) const { + return &m_data[y * m_width]; +} + +template +typename image::iterator image::operator[](size_type y) { + return &m_data[y * m_width]; +} + + +template +typename image::const_iterator image::begin() const { + return m_data.get(); +} + +template +typename image::iterator image::begin() { + return m_data.get(); +} + +template +typename image::const_iterator image::end() const { + return m_data.get() + m_width * m_height; +} + +template +typename image::iterator image::end() { + return m_data.get() + m_width * m_height; +} + +template +typename image::const_iterator image::cbegin() const { + return const_cast(begin()); +} + +template +typename image::const_iterator image::cend() const { + return const_cast(begin()); +} + +template +typename image::const_pointer image::data() const { + return m_data.get(); +} + +template +typename image::pointer image::data() { + return m_data.get(); +} + +} // namespace ztu diff --git a/include/util/line_parser.hpp b/include/util/line_parser.hpp new file mode 100644 index 0000000..b7d2826 --- /dev/null +++ b/include/util/line_parser.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include "util/for_each.hpp" + +namespace ztu +{ +template +struct prefixed_line_parser { + const std::string_view prefix; + F parse; +}; + +template +struct line_repeating_type {}; + +static constexpr auto is_repeating = line_repeating_type{}; +static constexpr auto is_not_repeating = line_repeating_type{}; + +template +prefixed_line_parser make_line_parser(std::string_view prefix, line_repeating_type, F&& f) +{ + return { prefix, std::forward(f) }; +} + +template +E parse_lines(std::istream& in, const bool pedantic, prefixed_line_parser&&... parsers) +{ + + auto ec = E{}; + + std::string line; + while (std::getline(in, line)) [[likely]] + { + parse_current_line: + + auto line_updated = false; + + for_each::argument( + [&](prefixed_line_parser&& parser) -> bool + { + if (line.starts_with(parser.prefix)) + { + const auto prefix_length = parser.prefix.length(); + ec = parser.parse(line.substr(prefix_length)); + + if constexpr (R) + { + while (std::getline(in, line)) + { + if (line.starts_with(parser.prefix)) [[likely]] + { + ec = parser.parse(line.substr(prefix_length)); + } + else [[unlikely]] + { + line_updated = true; + break; + } + } + } + + return true; + } + + return false; + }, + std::forward>(parsers)... + ); + + if (pedantic) { + if (ec != E{}) [[unlikely]] { + return ec; + } + } + + if (line_updated) + { + goto parse_current_line; + } + } + + return E{}; +} +} diff --git a/include/util/logger.hpp b/include/util/logger.hpp new file mode 100755 index 0000000..09ff75a --- /dev/null +++ b/include/util/logger.hpp @@ -0,0 +1,1742 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ztu { +namespace logger::settings { + +/** + * @brief Placeholder symbol used in format strings to signal value position. + */ +constexpr char format_symbol = '%'; + +/** + * @brief symbol for escaping the format symbol. + */ +constexpr char format_escape_symbol = '\\'; + +/** + * @brief Fall back to dynamic global config (instead of the static one) if none is provided. + */ +constexpr auto use_dynamic_global_config = false; + +/** + * @brief Throw a compile time error if there are errors in a given format string.. + */ +constexpr auto compile_time_exceptions = true; + +/** + * @brief Throw a run time error if there are errors in a given format string.. + */ +inline auto run_time_exceptions = false; + +} // namespace logger::settings + +namespace logger_internal { + +template +concept printeable = requires(std::ostream& os, const T& value) +{ + { os << value } -> std::same_as; +}; + +namespace format_errors { + +using type = std::uint8_t; +static constexpr type none{ 0b0 }, too_many_values = error_t{ 0b1 }, not_enough_values{ 0b10 }; + +} // namespace format_errors + +template +struct basic_format_string { + using string_view = std::basic_string_view; + using size_type = string_view::size_type; + + string_view view; + std::array seg_lens; + format_errors::type errors{ format_errors::none }; + + template + consteval basic_format_string(const CharT (&s)[N]) : view(s) { + compile(logger::settings::compile_time_exceptions); + } + + template + requires(std::is_convertible_v) + basic_format_string(const S& s) : view(s) { + compile(logger::settings::run_time_exceptions); + } + + constexpr void compile(const bool throw_exceptions) { + auto begin = view.data(); + const auto end = begin + view.length(); + for (auto& seg_len : seg_lens) { + const auto it = find_format_symbol(begin, end); + seg_len = it - begin; + if (it == end) { + if (throw_exceptions) { + throw std::invalid_argument("More values than placeholders."); + } + errors = format_errors::too_many_values; + begin = end; + } else { + begin = it + 1; + } + } + if (find_format_symbol(begin, end) != end) { + if (throw_exceptions) { + throw std::invalid_argument("Fewer values than placeholders."); + } + errors = format_errors::not_enough_values; + } + } + + static constexpr const CharT* find_format_symbol(const CharT* begin, const CharT* const end) { + auto escaped = false; + while (begin != end) { + if (*begin == logger::settings::format_escape_symbol) { + escaped = true; + } else { + if (*begin == logger::settings::format_symbol and not escaped) { + break; + } + escaped = false; + } + ++begin; + } + return begin; + } +}; + +template +using format_string = basic_format_string...>; + + +template + requires(N > 0) +struct basic_string_literal { + + std::array m_value{}; + + consteval basic_string_literal(const CharT (&str)[N]) { + std::copy_n(std::begin(str), N, m_value.begin()); + m_value.back() = '\0'; + } + + [[nodiscard]] constexpr std::basic_string_view view() const { + return { m_value.data(), N - 1 }; + } +}; + +template +using string_literal = basic_string_literal; + +using flags_int_t = std::uint16_t; + +} // namespace logger_internal + +/** + * @brief A lightweight logging library providing color-coded output and flexible configuration. + * + * @details This namespace serves as a wrapper around std::ostream, offering a + * simple yet powerful logging interface with support for ANSI color-coded log + * levels. It provides a global logging context as well as customizable logger + * contexts, allowing users to configure output streams, log levels and more. + */ +namespace logger { + +/** + * @brief Enumeration representing the log levels. + * + * The log levels are ordered from least verbose to most verbose, as follows: + * - @c mute: Indicates that no log messages should be emitted. + * - @c generic: A catch-all levels for generic log messages via 'println'. + * - @c error: Used for critical errors that require immediate attention. + * - @c warn: Used for potential issues that may need attention. + * - @c info: Used for significant events and information about the process. + * - @c log: General log messages providing information about the application's operation. + * - @c debug: Detailed debug information helpful for troubleshooting. + */ +enum class level : std::uint8_t { + mute = 0, + generic = 1, + error = 2, + warn = 3, + info = 4, + log = 5, + debug = 6 +}; + +/** + * @brief Enumeration of feature flag for the logger class. + * + * The `flag` enum is a set of feature flag used in the logger class to control the behavior of log message output. + * Each flag corresponds to a specific feature or information component that can be included in log messages, + * providing a flexible and customizable way to tailor the levels of detail included in log outputs + * based on the specific needs of the application. + * + * @c none: Disables all flag. Log messages will only include the message without any additional information. + * @c all: Enables all available flag. Log messages will include all following features: + * @c colors: Enables color-coding for log messages, providing visual cues for different log levels. + * @c locking: Enables the use of a spin lock mechanism for thread-safe log message output. + * @c identifier -----------------------------------------------------------------------------------+ + * @c thread_id -----------------------------------------------------------------------+ | + * @c function_name ------------------------------------------------------------+ | | + * @c line_number ------------------------------------------------------+ | | | + * @c filename -----------------------------------------------+ | | | | + * @c level_name ----------------------------------------+ | | | | | + * @c timestamp -------------------------------+ | | | | | | + * @c time ----------------+ | | | | | | | + * @c date ----+ | | | | | | | | + * | | | | | | | | | + * example message: [2024-1-8][19:44:9.535][1704739449535][log][main.cpp:61][main][140654793804672][tester] Hello World + */ +enum class flag : logger_internal::flags_int_t { + none = 0, + all = std::numeric_limits::max(), + colors = (1 << 0), + locking = (1 << 1), + date = (1 << 2), + time = (1 << 3), + timestamp = (1 << 4), + level_name = (1 << 5), + filename = (1 << 6), + line_number = (1 << 7), + function_name = (1 << 8), + thread_id = (1 << 9) +}; + +} // namespace logger +} // namespace ztu + +[[nodiscard]] constexpr ztu::logger::flag operator|(const ztu::logger::flag& a, const ztu::logger::flag& b) { + return static_cast( + static_cast(a) | static_cast(b) + ); +} + +[[nodiscard]] constexpr ztu::logger::flag operator&(const ztu::logger::flag& a, const ztu::logger::flag& b) { + return static_cast( + static_cast(a) & static_cast(b) + ); +} + +[[nodiscard]] constexpr ztu::logger::flag operator^(const ztu::logger::flag& a, const ztu::logger::flag& b) { + return static_cast( + static_cast(a) ^ static_cast(b) + ); +} + +[[nodiscard]] constexpr ztu::logger::flag operator~(const ztu::logger::flag& a) { + return static_cast(~static_cast(a)); +} + +constexpr ztu::logger::flag& operator|=(ztu::logger::flag& a, const ztu::logger::flag& b) { + return a = a | b; +} + +constexpr ztu::logger::flag& operator&=(ztu::logger::flag& a, const ztu::logger::flag& b) { + return a = a & b; +} + +constexpr ztu::logger::flag& operator^=(ztu::logger::flag& a, const ztu::logger::flag& b) { + return a = a ^ b; +} + +inline std::ostream& operator<<(std::ostream& out, ztu::logger::level level) { + switch (level) { + using enum ztu::logger::level; + case mute: + return out << "mute"; + case error: + return out << "error"; + case warn: + return out << "warn"; + case info: + return out << "info"; + case log: + return out << "log"; + case debug: + return out << "debug"; + case generic: + return out << "generic"; + default: + return out << "unknown(" << std::to_string(static_cast(level)) << ")"; + } +} + +namespace ztu { +namespace logger { + +/** + * @brief Context structure for configuring the behavior of the logger class. + * + * The `context` struct provides a means to configure the logger's behavior by + * specifying the output streams, log message formatting flag, log levels, and + * an optional spin lock for thread safety. + * + * @c out: Pointer to the output stream for standard log messages. + * @c err: Pointer to the output stream for error log messages. + * @c lock: Atomic flag used for thread-safe log message output when the `locking` flag is enabled. + */ +struct context { + std::ostream* out; + std::ostream* err; + std::atomic_flag lock = ATOMIC_FLAG_INIT; + + inline context(std::ostream&, std::ostream&); +}; + +/** + * @brief Dynamic config structure for configuring the behavior of the logger class. + * + * The `context` struct provides a means to configure the logger's behavior by sspecifying the output streams, + * log message formatting flag, log levels, and an optional spin lock for thread safety. + * + * @param flag Flags representing the formatting options for log messages. See the flag enum for details. + * @param threshold Log levels setting, controlling the verbosity of logged messages. See the levels enum for details. + * @param identifier Atomic flag used for thread-safe log message output when the `locking` flag is enabled. + */ +struct dynamic_config { + flag flags; + level threshold; + std::string identifier{}; +}; + +/** + * @brief Static config structure for configuring the behavior of the logger class. + * + * The `context` struct provides a means to configure the logger's behavior by sspecifying the output streams, + * log message formatting flag, log levels, and an optional spin lock for thread safety. + * + * @tparam Flags Flags representing the formatting options for log messages. See the flag enum for details. + * @tparam Threshold Log levels setting, controlling the verbosity of logged messages. See the levels enum for details. + * @tparam Identifier Atomic flag used for thread-safe log message output when the `locking` flag is enabled. + */ +template +struct static_config { + static constexpr auto flags = Flags; + static constexpr auto threshold = Threshold; + static constexpr auto identifier = Identifier.view(); +}; + +/** + * @brief Returns the global fallback context used in all logging functions. + * + * @return A reference to the global context. + */ +inline context& global_context(); + +/** + * @brief Returns the global dynamic fallback config used in all logging functions. + * + * @return A reference to the global dynamic config. + */ +inline dynamic_config& global_dynamic_config(); + +/** + * @brief The global static fallback config used in all logging functions. + */ +inline constexpr auto global_static_config = static_config< + (flag::colors | flag::locking /*| flag::time*/ | flag::level_name | flag::filename | flag::line_number), + level::debug, + "">{}; + +namespace functions { +/** + * @brief Formats and logs an error message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the error message, supporting placeholders. + * @param args The values to be formatted and included in the error message. + */ +template +struct error { + explicit error( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit error( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit error( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit error( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit error( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit error( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +error(logger_internal::format_string, Args&&...) -> error; + +template +error(context& ctx, logger_internal::format_string, Args&&...) -> error; + +template +error(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> error; + +template +error(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> error; + +template +error(const static_config& cfg, logger_internal::format_string, Args&&...) + -> error; + +template +error( + context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&... +) -> error; + +/** + * @brief Formats and logs a warn message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the warn message, supporting placeholders. + * @param args The values to be formatted and included in the warn message. + */ +template +struct warn { + explicit warn( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit warn( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit warn( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit warn( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit warn( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit warn( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +warn(logger_internal::format_string, Args&&...) -> warn; + +template +warn(context& ctx, logger_internal::format_string, Args&&...) -> warn; + +template +warn(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> warn; + +template +warn(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> warn; + +template +warn(const static_config& cfg, logger_internal::format_string, Args&&...) + -> warn; + +template +warn( + context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&... +) -> warn; + +/** + * @brief Formats and logs an info message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the info message, supporting placeholders. + * @param args The values to be formatted and included in the info message. + */ +template +struct info { + explicit info( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit info( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit info( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit info( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit info( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit info( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +info(logger_internal::format_string, Args&&...) -> info; + +template +info(context& ctx, logger_internal::format_string, Args&&...) -> info; + +template +info(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> info; + +template +info(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> info; + +template +info(const static_config& cfg, logger_internal::format_string, Args&&...) + -> info; + +template +info( + context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&... +) -> info; + +/** + * @brief Formats and logs a log message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the log message, supporting placeholders. + * @param args The values to be formatted and included in the log message. + */ +template +struct log { + explicit log( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit log( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit log( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit log( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit log( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit log( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +log(logger_internal::format_string, Args&&...) -> log; + +template +log(context& ctx, logger_internal::format_string, Args&&...) -> log; + +template +log(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> log; + +template +log(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> log; + +template +log(const static_config& cfg, logger_internal::format_string, Args&&...) + -> log; + +template +log(context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&...) -> log; + +/** + * @brief Formats and logs a debug message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the debug message, supporting placeholders. + * @param args The values to be formatted and included in the debug message. + */ +template +struct debug { + explicit debug( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit debug( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit debug( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit debug( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit debug( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit debug( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +debug(logger_internal::format_string, Args&&...) -> debug; + +template +debug(context& ctx, logger_internal::format_string, Args&&...) -> debug; + +template +debug(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> debug; + +template +debug(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> debug; + +template +debug(const static_config& cfg, logger_internal::format_string, Args&&...) + -> debug; + +template +debug( + context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&... +) -> debug; + +/** + * @brief Formats and logs a generic message using the @c global_handle. + * + * The format string may contain placeholders (@c fmt_symbol) + * replaced by corresponding values provided in the arguments (@c args). + * + * @tparam Args Variadic template for the types of arguments passed to the format string. + * @param ctx The logging context used for writing out the formatted message. + * @param cfg The config for the message formatting and filtering. + * @param fmt The format string for the generic message, supporting placeholders. + * @param args The values to be formatted and included in the generic message. + */ +template +struct println { + explicit println( + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit println( + context& ctx, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit println( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit println( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + explicit println( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); + + template + explicit println( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location = std::source_location::current() + ); +}; + +template +println(logger_internal::format_string, Args&&...) -> println; + +template +println(context& ctx, logger_internal::format_string, Args&&...) -> println; + +template +println(const dynamic_config& cfg, logger_internal::format_string, Args&&...) -> println; + +template +println(context& ctx, const dynamic_config& cfg, logger_internal::format_string, Args&&...) + -> println; + +template +println(const static_config& cfg, logger_internal::format_string, Args&&...) + -> println; + +template +println( + context& ctx, + const static_config& cfg, + logger_internal::format_string, + Args&&... +) -> println; + +} // namespace functions + +} // namespace logger + +namespace logger_internal { + +enum class color : std::uint8_t { + reset = 0, + black = 30, + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + white = 37 +}; + +constexpr std::size_t num_digits(std::uint32_t n, const std::uint32_t base = 10) { + if (n < base) { + return 1; + } + + auto digits = std::size_t{}; + while (n > 0) { + n /= base; + ++digits; + } + + return digits; +} + +template +constexpr auto int_to_str() { + auto str = std::array{}; + + auto index = str.size() - 1; + auto num = N; + do { + str[index] = static_cast('0' + num % B); + num /= B; + } while (index--); + + return str; +} + +template +constexpr auto create_ansi_color() { + constexpr auto color_index = static_cast(color); + constexpr auto color_index_str = int_to_str(); + + constexpr auto prefix = std::string_view("\x1B["); + constexpr auto postfix = [&]() -> std::string_view { + if constexpr (bright) { + return ";1m"; + } else { + return "m"; + } + }(); + + constexpr auto color_str_length = (prefix.size() + color_index_str.size() + postfix.size()); + std::array color_str{}; + + auto it = color_str.begin(); + const auto append = [&it](auto begin, const auto end) { + while (begin != end) { + *it++ = *begin++; + } + }; + + append(prefix.begin(), prefix.end()); + append(color_index_str.begin(), color_index_str.end()); + append(postfix.begin(), postfix.end()); + + return color_str; +} + +inline constexpr auto reset_color_array = create_ansi_color(); +inline constexpr auto reset_color = std::string_view(reset_color_array.data(), reset_color_array.size()); + +inline constexpr auto internal_color_array = create_ansi_color(); +inline constexpr auto internal_color = std::string_view(internal_color_array.data(), internal_color_array.size()); + +inline constexpr std::size_t level_count = 6; +inline constexpr std::array level_names{ "generic", "error", "warning", + "info", "log", "debug" }; + +template +constexpr auto create_color_tuple() { + return std::make_tuple(create_ansi_color()...); +} + +template +constexpr auto build_string_lookup(const std::tuple& string_tuple, std::index_sequence) { + return std::array{ std::string_view(std::get(string_tuple).data(), std::get(string_tuple).size())... }; +} + +inline constexpr auto level_dark_color_tuple = create_color_tuple< + false, + color::white, + color::red, + color::yellow, + color::green, + color::blue, + color::magenta>(); + +inline constexpr auto level_dark_color_lookup = build_string_lookup( + level_dark_color_tuple, std::make_index_sequence{} +); + +inline constexpr auto level_bright_color_tuple = create_color_tuple< + true, + color::white, + color::red, + color::yellow, + color::green, + color::blue, + color::magenta>(); + +inline constexpr auto level_bright_color_lookup = build_string_lookup( + level_bright_color_tuple, std::make_index_sequence{} +); + +inline void print_escaped_string( + std::ostream& out, + const std::string_view& reset_clr, + const std::string_view& internal_clr, + const char* begin, + const char* const end +) { + auto escaped = false; + auto it = begin; + while (it != end) { + if (*it == logger::settings::format_escape_symbol) { + escaped = true; + } else { + if (*it == logger::settings::format_symbol) { + out.write(begin, it - begin - escaped); + begin = it + 1 - escaped; + if (not escaped) { + out << internal_clr << logger::settings::format_symbol << reset_clr; + } + } + escaped = false; + } + ++it; + } + out.write(begin, it - begin); +} + +template +void print_format_impl( + std::ostream& out, + const std::string_view&, + std::string_view& reset_clr, + const std::string_view& internal_clr, + std::size_t pos, + bool&, + const Fmt& fmt +) { + print_escaped_string(out, reset_clr, internal_clr, &fmt.view[pos], fmt.view.data() + fmt.view.length()); +} + +template +void print_format_impl( + std::ostream& out, + const std::string_view& value_clr, + std::string_view& reset_clr, + const std::string_view& internal_clr, + std::size_t pos, + bool& excess_value, + Fmt& fmt, + Arg&& arg, + Rest&&... rest +) { + const auto arg_index = fmt.seg_lens.size() - sizeof...(Rest) - 1; + const auto len = fmt.seg_lens[arg_index]; + const auto str = &fmt.view[pos]; + print_escaped_string(out, reset_clr, internal_clr, str, str + len); + pos += len; + if (pos == fmt.view.length()) { + if (excess_value) { + out << ','; + } else { + out << internal_clr << " <["; + excess_value = true; + } + reset_clr = internal_clr; + } else { + ++pos; + } + out << value_clr << arg << reset_clr; + print_format_impl(out, value_clr, reset_clr, internal_clr, pos, excess_value, fmt, std::forward(rest)...); +} + +template +void print_format( + std::ostream& out, + const std::string_view& value_clr, + std::string_view& reset_clr, + const std::string_view& internal_clr, + Fmt& fmt, + Args&&... args +) { + const auto reset = reset_clr; + auto excess_value = false; + print_format_impl(out, value_clr, reset_clr, internal_clr, 0ul, excess_value, fmt, std::forward(args)...); + if (fmt.errors & format_errors::too_many_values) { + out << "] Excess value(s)>"; + } else if (fmt.errors & format_errors::not_enough_values) { + out << internal_clr << " "; + } + out << reset; +} + +template +void println_impl( + logger::context& ctx, + const logger::dynamic_config& cfg, + const std::source_location& location, + logger::level threshold, + format_string fmt, + Args&&... args +) { + using namespace logger; + + const auto current_level_index = static_cast(cfg.threshold); + auto level_index = static_cast(threshold); + if (current_level_index < level_index) { + return; + } + + if ((cfg.flags & flag::locking) != flag::none) { + while (ctx.lock.test_and_set(std::memory_order_relaxed)) { + } + } + + level_index = std::min(level_index, static_cast(level_count)) - 1; + + std::string_view bright_clr, dark_clr, reset_clr, internal_clr; + if ((cfg.flags & flag::colors) != flag::none) { + bright_clr = level_bright_color_lookup[level_index]; + dark_clr = level_dark_color_lookup[level_index]; + reset_clr = reset_color; + internal_clr = internal_color; + } + + auto& stream = (threshold == level::warn or threshold == level::error) ? *ctx.err : *ctx.out; + + const auto print_prefix = [&](const auto&... values) { + stream << '[' << bright_clr; + ((stream << values), ...) << reset_clr << ']'; + }; + + static constexpr auto needs_clock = flag::date | flag::time | flag::timestamp; + if ((cfg.flags & needs_clock) != flag::none) { + namespace chr = std::chrono; + + using clock = chr::system_clock; + const auto now = clock::now(); + + static constexpr auto needs_time = flag::date | flag::time; + if ((cfg.flags & needs_time) != flag::none) { + const auto time = clock::to_time_t(now); + const auto local_time = std::localtime(&time); + + if ((cfg.flags & flag::date) != flag::none) { + print_prefix(1'900 + local_time->tm_year, '-', 1 + local_time->tm_mon, '-', local_time->tm_mday); + } + + if ((cfg.flags & flag::time) != flag::none) { + const auto truncated = std::chrono::system_clock::from_time_t(time); + const auto millis = chr::duration_cast(now - truncated).count(); + + print_prefix(local_time->tm_hour, ':', local_time->tm_min, ':', local_time->tm_sec, '.', millis); + } + } + + if ((cfg.flags & flag::timestamp) != flag::none) { + const auto timestamp = chr::duration_cast(now.time_since_epoch()); + print_prefix(timestamp.count()); + } + } + + if ((cfg.flags & flag::level_name) != flag::none) { + print_prefix(level_names[level_index]); + } + + if ((cfg.flags & flag::filename) != flag::none) { + if ((cfg.flags & flag::line_number) != flag::none) { + print_prefix(' ', location.file_name(), ':', std::dec, location.line()); + } else { + print_prefix(location.file_name()); + } + } else if ((cfg.flags & flag::line_number) != flag::none) { + print_prefix(std::dec, location.line()); + } + + if ((cfg.flags & flag::function_name) != flag::none) { + print_prefix(location.function_name()); + } + + if ((cfg.flags & flag::thread_id) != flag::none) { + print_prefix(std::this_thread::get_id()); + } + + if (not cfg.identifier.empty()) { + print_prefix(cfg.identifier); + } + + static constexpr auto prefix_flags = ~(flag::colors | flag::locking); + if ((cfg.flags & prefix_flags) != flag::none or not cfg.identifier.empty()) { + stream << ' '; + } + + logger_internal::print_format(stream, dark_clr, reset_clr, internal_clr, fmt, std::forward(args)...); + stream << '\n'; + + ctx.lock.clear(); +} + +template +void println_impl( + logger::context& ctx, + const logger::static_config&, + const std::source_location& location, + logger::level threshold, + format_string fmt, + Args&&... args +) { + using namespace logger; + + constexpr auto current_level_index = static_cast(Threshold); + auto level_index = static_cast(threshold); + if (current_level_index < level_index) { + return; + } + + if constexpr ((Flags & flag::locking) != flag::none) { + while (ctx.lock.test_and_set(std::memory_order_relaxed)) { + } + } + + level_index = std::min(level_index, static_cast(level_count)) - 1; + + std::string_view bright_clr, dark_clr, reset_clr, internal_clr; + if constexpr ((Flags & flag::colors) != flag::none) { + bright_clr = level_bright_color_lookup[level_index]; + dark_clr = level_dark_color_lookup[level_index]; + reset_clr = reset_color; + internal_clr = internal_color; + } + + auto& stream = (threshold == level::warn or threshold == level::error) ? *ctx.err : *ctx.out; + + const auto print_prefix = [&](const auto&... values) { + stream << '[' << bright_clr; + ((stream << values), ...) << reset_clr << ']'; + }; + + static constexpr auto needs_clock = flag::date | flag::time | flag::timestamp; + if constexpr ((Flags & needs_clock) != flag::none) { + namespace chr = std::chrono; + + using clock = chr::system_clock; + const auto now = clock::now(); + + static constexpr auto needs_time = flag::date | flag::time; + if constexpr ((Flags & needs_time) != flag::none) { + const auto time = clock::to_time_t(now); + const auto local_time = std::localtime(&time); + + if constexpr ((Flags & flag::date) != flag::none) { + print_prefix(1'900 + local_time->tm_year, '-', 1 + local_time->tm_mon, '-', local_time->tm_mday); + } + + if constexpr ((Flags & flag::time) != flag::none) { + const auto truncated = std::chrono::system_clock::from_time_t(time); + const auto millis = chr::duration_cast(now - truncated).count(); + + print_prefix(local_time->tm_hour, ':', local_time->tm_min, ':', local_time->tm_sec, '.', millis); + } + } + + if ((Flags & flag::timestamp) != flag::none) { + const auto timestamp = chr::duration_cast(now.time_since_epoch()); + print_prefix(timestamp.count()); + } + } + + if constexpr ((Flags & flag::level_name) != flag::none) { + print_prefix(level_names[level_index]); + } + + if constexpr ((Flags & flag::filename) != flag::none) { + if constexpr ((Flags & flag::line_number) != flag::none) { + print_prefix(' ', location.file_name(), ':', std::dec, location.line()); + } else { + print_prefix(location.file_name()); + } + } else if constexpr ((Flags & flag::line_number) != flag::none) { + print_prefix(std::dec, location.line()); + } + + if constexpr ((Flags & flag::function_name) != flag::none) { + print_prefix(location.function_name()); + } + + if constexpr ((Flags & flag::thread_id) != flag::none) { + print_prefix(std::this_thread::get_id()); + } + + constexpr auto identifier = Identifier.view(); + if constexpr (not identifier.empty()) { + print_prefix(identifier); + } + + static constexpr auto prefix_flags = ~(flag::colors | flag::locking); + if constexpr ((Flags & prefix_flags) != flag::none or not identifier.empty()) { + stream << ' '; + } + + logger_internal::print_format(stream, dark_clr, reset_clr, internal_clr, fmt, std::forward(args)...); + stream << '\n'; + + ctx.lock.clear(); +} +} // namespace logger_internal + +namespace logger { + +context::context(std::ostream& new_out, std::ostream& new_err) : out{ &new_out }, err{ &new_err } { +} + +context& global_context() { + static auto ctx = context(std::cout, std::cerr); + return ctx; +} + +dynamic_config& global_dynamic_config() { + static auto cfg = dynamic_config{ (flag::colors | flag::locking | flag::time | flag::level_name | flag::filename | + flag::line_number), + level::debug, + "" }; + return cfg; +} + +namespace functions { +template +error::error( + logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl( + global_context(), + global_dynamic_config(), + location, + level::error, + fmt, + std::forward(args)... + ); + } else { + println_impl(global_context(), global_static_config, location, level::error, fmt, std::forward(args)...); + } +} + +template +error::error( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::error, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::error, fmt, std::forward(args)...); + } +} + +template +error::error( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::error, fmt, std::forward(args)...); +} + +template +template +error::error( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::error, fmt, std::forward(args)...); +} + +template +error::error( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::error, fmt, std::forward(args)...); +} + +template +template +error::error( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::error, fmt, std::forward(args)...); +} + +template +warn::warn(logger_internal::format_string fmt, Args&&... args, const std::source_location& location) { + if constexpr (settings::use_dynamic_global_config) { + println_impl( + global_context(), + global_dynamic_config(), + location, + level::warn, + fmt, + std::forward(args)... + ); + } else { + println_impl(global_context(), global_static_config, location, level::warn, fmt, std::forward(args)...); + } +} + +template +warn::warn( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::warn, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::warn, fmt, std::forward(args)...); + } +} + +template +warn::warn( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::warn, fmt, std::forward(args)...); +} + +template +template +warn::warn( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::warn, fmt, std::forward(args)...); +} + +template +warn::warn( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::warn, fmt, std::forward(args)...); +} + +template +template +warn::warn( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::warn, fmt, std::forward(args)...); +} + +template +info::info(logger_internal::format_string fmt, Args&&... args, const std::source_location& location) { + if constexpr (settings::use_dynamic_global_config) { + println_impl( + global_context(), + global_dynamic_config(), + location, + level::info, + fmt, + std::forward(args)... + ); + } else { + println_impl(global_context(), global_static_config, location, level::info, fmt, std::forward(args)...); + } +} + +template +info::info( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::info, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::info, fmt, std::forward(args)...); + } +} + +template +info::info( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::info, fmt, std::forward(args)...); +} + +template +template +info::info( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::info, fmt, std::forward(args)...); +} + +template +info::info( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::info, fmt, std::forward(args)...); +} + +template +template +info::info( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::info, fmt, std::forward(args)...); +} + +template +log::log(logger_internal::format_string fmt, Args&&... args, const std::source_location& location) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(global_context(), global_dynamic_config(), location, level::log, fmt, std::forward(args)...); + } else { + println_impl(global_context(), global_static_config, location, level::log, fmt, std::forward(args)...); + } +} + +template +log::log( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::log, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::log, fmt, std::forward(args)...); + } +} + +template +log::log( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::log, fmt, std::forward(args)...); +} + +template +template +log::log( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::log, fmt, std::forward(args)...); +} + +template +log::log( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::log, fmt, std::forward(args)...); +} + +template +template +log::log( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::log, fmt, std::forward(args)...); +} + +template +debug::debug( + logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl( + global_context(), + global_dynamic_config(), + location, + level::debug, + fmt, + std::forward(args)... + ); + } else { + println_impl(global_context(), global_static_config, location, level::debug, fmt, std::forward(args)...); + } +} + +template +debug::debug( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::debug, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::debug, fmt, std::forward(args)...); + } +} + +template +debug::debug( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::debug, fmt, std::forward(args)...); +} + +template +template +debug::debug( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::debug, fmt, std::forward(args)...); +} + +template +debug::debug( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::debug, fmt, std::forward(args)...); +} + +template +template +debug::debug( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::debug, fmt, std::forward(args)...); +} + +template +println::println( + logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl( + global_context(), + global_dynamic_config(), + location, + level::generic, + fmt, + std::forward(args)... + ); + } else { + println_impl( + global_context(), + global_static_config, + location, + level::generic, + fmt, + std::forward(args)... + ); + } +} + +template +println::println( + context& ctx, logger_internal::format_string fmt, Args&&... args, const std::source_location& location +) { + if constexpr (settings::use_dynamic_global_config) { + println_impl(ctx, global_dynamic_config(), location, level::generic, fmt, std::forward(args)...); + } else { + println_impl(ctx, global_static_config, location, level::generic, fmt, std::forward(args)...); + } +} + +template +println::println( + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::generic, fmt, std::forward(args)...); +} + +template +template +println::println( + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(global_context(), cfg, location, level::generic, fmt, std::forward(args)...); +} + +template +println::println( + context& ctx, + const dynamic_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::generic, fmt, std::forward(args)...); +} + +template +template +println::println( + context& ctx, + const static_config& cfg, + logger_internal::format_string fmt, + Args&&... args, + const std::source_location& location +) { + println_impl(ctx, cfg, location, level::generic, fmt, std::forward(args)...); +} + +} // namespace functions + +using namespace functions; + +} // namespace logger +} // namespace ztu diff --git a/include/util/result.hpp b/include/util/result.hpp new file mode 100644 index 0000000..f571064 --- /dev/null +++ b/include/util/result.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +namespace ztu { + +template +using result = std::expected; + +/* +template +class result { + static constexpr auto value_index = 0; + static constexpr auto error_index = 1; + +public: + constexpr result(T value); + constexpr result(const std::error_code& error); + + template + void emplace(Args&&... args); + + [[nodiscard]] constexpr bool ok() const; + + [[nodiscard]] constexpr std::error_code error() const; + + [[nodiscard]] constexpr const T& value() const; + + [[nodiscard]] constexpr T& value(); + + [[nodiscard]] constexpr T&& value() &&; + + [[nodiscard]] operator bool() const; + + [[nodiscard]] const T& operator*() const; + + [[nodiscard]] T& operator*(); + + [[nodiscard]] T&& operator*() &&; + + [[nodiscard]] bool operator==(const T& rhs) const; + + [[nodiscard]] bool operator==(const std::error_code& rhs) const; + + template + auto map(Func&& func) const -> result + { + if (ok()) return func(value()); + return error(); + } + +private: + std::variant m_data; +}; + +// Member function definitions + +template +constexpr result::result(T value) + : m_data{ std::in_place_index_t{}, std::move(value) } +{} + +template +constexpr result::result(const std::error_code& error) + : m_data{ std::in_place_index_t{}, error } +{} + +template +template +void result::emplace(Args&&... args) { + m_data.template emplace(std::forward(args)...); +} + +template +template +auto result::map(Func&& func) const -> result { + if (ok()) return func(value()); + return error(); +} + +template +constexpr bool result::ok() const { + return m_data.index() == value_index; +} + +template +constexpr std::error_code result::error() const { + auto error_ptr = std::get_if(m_data); + return error_ptr ? *error_ptr : std::error_code{}; +} + +template +constexpr const T& result::value() const { + return std::get(m_data); +} + +template +constexpr T& result::value() { + return std::get(m_data); +} + +template +constexpr T&& result::value() && { + return std::get(std::move(m_data)); +} + +template +constexpr result::operator bool() const { + return ok(); +} + +template +const T& result::operator*() const { + return *std::get_if(m_data); +} + +template +T& result::operator*() { + return *std::get_if(m_data); +} + +template +T&& result::operator*() && { + return *std::get_if(std::move(m_data)); +} + +template +bool result::operator==(const T& rhs) const { + return ok() and value() == rhs; +} + +template +bool result::operator==(const std::error_code& rhs) const { + return not ok() and error() == rhs; +}*/ + +} diff --git a/include/util/specialised_lambda.hpp b/include/util/specialised_lambda.hpp new file mode 100644 index 0000000..6fa1b78 --- /dev/null +++ b/include/util/specialised_lambda.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace ztu +{ +template +struct specialised_lambda : Lambdas... { using Lambdas::operator()...; }; + +template +specialised_lambda(Lambdas...) -> specialised_lambda; +} diff --git a/include/util/string_indexer.hpp b/include/util/string_indexer.hpp new file mode 100755 index 0000000..5bd63af --- /dev/null +++ b/include/util/string_indexer.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "util/uix.hpp" +#include + +template +class string_indexer { +private: + struct index_type { + unsigned int hash; + ztu::usize index; + + [[nodiscard]] inline constexpr auto operator<=>(const index_type &other) const { + return hash <=> other.hash; + } + + [[nodiscard]] inline constexpr auto operator==(const index_type &other) const { + return hash == other.hash; + } + + [[nodiscard]] inline constexpr auto operator<=>(const unsigned other_hash) const { + return hash <=> other_hash; + } + }; + + [[nodiscard]] inline constexpr static unsigned hash(std::span str); + +public: + template + requires (sizeof...(Ts) == NumKeys) + consteval explicit string_indexer(const Ts&... keys) noexcept; + + [[nodiscard]] inline constexpr std::optional index_of(std::span str) const; + + [[nodiscard]] inline constexpr std::optional name_of(ztu::usize index) const; + + [[nodiscard]] inline constexpr std::span keys() const; + +private: + std::array m_lookup{}; + std::array m_keys{}; +}; + +template +[[nodiscard]] inline constexpr unsigned string_indexer::hash(std::span str) { + + unsigned prime = 0x1000193; + unsigned hashed = 0x811c9dc5; + + for (const auto &c : str) { + hashed = hashed ^ c; + hashed *= prime; + } + + return hashed; +} + +template +template requires (sizeof...(Ts) == NumKeys) +consteval string_indexer::string_indexer(const Ts&... keys) noexcept { + + ztu::for_each::indexed_argument([&](const auto &key) { + // Since std::string_view does only truncate the '\0' of strings in the 'const char*' constructor + // and does not deem otherwise equal views of truncated and untruncated strings equal, + // all strings need to be truncated before constructing the view. + const auto begin = std::begin(key), end = std::end(key); + m_keys[Index] = { begin, std::find(begin, end, '\0') }; + m_lookup[Index] = { hash(m_keys[Index] ), Index }; + return false; + }, keys...); + + std::sort(m_lookup.begin(), m_lookup.end()); + + auto it = m_lookup.begin(); + while ((it = std::adjacent_find(it, m_lookup.end())) != m_lookup.end()) { + const auto match = it->hash; + for (auto it_a = it + 1; it_a != m_lookup.end() && it_a->hash == match; it_a++) { + const auto &key_a = m_keys[it_a->index]; + for (auto it_b = it; it_b != it_a; it_b++) { + const auto &key_b = m_keys[it_b->index]; + if (key_a == key_b) { + throw std::logic_error("Duplicate keys"); + } + } + } + } +} + +template +[[nodiscard]] inline constexpr std::optional string_indexer::index_of(std::span str) const { + const auto sv = std::string_view(str.begin(), std::find(str.begin(), str.end(), '\0')); + + const auto hashed = hash(sv); + const auto it = std::lower_bound(m_lookup.begin(), m_lookup.end(), hashed); + + if (it == m_lookup.end() or hashed != it->hash) + return std::nullopt; + + do [[unlikely]] { + const auto candidate_index = it->index; + if (m_keys[candidate_index] == sv) [[likely]] { + return candidate_index; + } + } while (it < m_lookup.end() && it->hash == hashed); + + return std::nullopt; +} + +template +[[nodiscard]] inline constexpr std::optional string_indexer::name_of(ztu::usize index) const { + if (index < NumKeys) { + return m_keys[index]; + } else { + return std::nullopt; + } +} + +template +[[nodiscard]] inline constexpr std::span string_indexer::keys() const { + return { m_keys }; +} diff --git a/include/util/string_list.hpp b/include/util/string_list.hpp new file mode 100644 index 0000000..6ba8808 --- /dev/null +++ b/include/util/string_list.hpp @@ -0,0 +1,259 @@ +#pragma once + +#include +#include +#include +#include + +namespace ztu +{ + +class string_list_iterator +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = std::string_view; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + string_list_iterator( + std::string_view buffer, + std::span lengths, + std::size_t index, + std::size_t char_offset) + : m_buffer{ buffer }, m_lengths{ lengths }, m_index{ index }, m_char_offset{ char_offset } {} + + value_type operator*() const + { + return m_buffer.substr(m_char_offset, m_lengths[m_index]); + } + + string_list_iterator& operator++() + { + ++m_index; + m_char_offset += m_lengths[m_index]; + return *this; + } + + string_list_iterator operator++(int) + { + auto tmp = *this; + ++(*this); + return tmp; + } + + string_list_iterator& operator--() + { + --m_index; + m_char_offset -= m_lengths[m_index]; + return *this; + } + + string_list_iterator operator--(int) + { + auto tmp = *this; + --(*this); + return tmp; + } + + string_list_iterator& operator+=(difference_type n) + { + const auto negative = n < difference_type{ 0 }; + const auto positive = n > difference_type{ 0 }; + const auto step = difference_type{ positive } - difference_type{ negative }; + n = negative ? -n : n; + + while (n--) + { + m_char_offset += step * m_lengths[m_index]; + m_index += step; + } + return *this; + } + + string_list_iterator& operator-=(const difference_type n) + { + return *this += -n; + } + + string_list_iterator operator+(const difference_type n) const + { + auto tmp = *this; + return tmp += n; + } + + string_list_iterator operator-(const difference_type n) const + { + auto tmp = *this; + return tmp -= n; + } + + difference_type operator-(const string_list_iterator& other) const + { + return static_cast(m_index) - static_cast(other.m_index); + } + + value_type operator[](const difference_type n) const + { + auto tmp = *this; + tmp += n; + return *tmp; + } + + bool operator==(const string_list_iterator& other) const { + return m_index == other.m_index && m_char_offset == other.m_char_offset; + } + + bool operator!=(const string_list_iterator& other) const { + return !(*this == other); + } + + bool operator<(const string_list_iterator& other) const { + return m_index < other.m_index; + } + + bool operator<=(const string_list_iterator& other) const { + return m_index <= other.m_index; + } + + bool operator>(const string_list_iterator& other) const { + return m_index > other.m_index; + } + + bool operator>=(const string_list_iterator& other) const { + return m_index >= other.m_index; + } + +private: + std::string_view m_buffer; + std::span m_lengths; + std::size_t m_index; + std::size_t m_char_offset; +}; + +class string_list +{ + using index_type = std::uint32_t; + using iterator = string_list_iterator; + +public: + string_list() = default; + + string_list(std::initializer_list init) + { + for (const auto& str : init) { + push_back(str); + } + } + + void reserve(const std::size_t characters, const std::size_t lengths) + { + m_buffer.reserve(characters); + m_lengths.reserve(lengths); + } + + std::size_t character_count() const + { + return m_buffer.size(); + } + + void push_back(const std::string_view& str) + { + m_buffer.reserve(m_buffer.size() + str.length() + 1); + m_buffer.insert(m_buffer.end(), str.begin(), str.end()); + m_buffer.push_back('\0'); + m_lengths.push_back(static_cast(str.size())); + } + + void push_back(const string_list& list) + { + m_buffer.insert(m_buffer.end(), list.m_buffer.begin(), list.m_buffer.end()); + m_lengths.insert(m_lengths.end(), list.m_lengths.begin(), list.m_lengths.end()); + } + + void pop_back() + { + m_buffer.resize(m_buffer.size() - m_lengths.back()); + m_lengths.pop_back(); + } + + [[nodiscard]] std::string_view operator[](index_type index) const + { + auto offset = std::size_t{}; + for (index_type i{}; i != index; ++i) + { + offset += m_lengths[i]; + } + + return { &m_buffer[offset], m_lengths[index] }; + } + + [[nodiscard]] index_type size() const + { + return m_lengths.size(); + } + + [[nodiscard]] bool empty() const + { + return m_lengths.empty(); + } + + void clear() + { + m_buffer.clear(); + m_lengths.clear(); + } + + void reserve(index_type new_cap) + { + m_buffer.reserve(new_cap); + m_lengths.reserve(new_cap); + } + + [[nodiscard]] index_type capacity() const + { + return m_buffer.capacity(); + } + + [[nodiscard]] iterator begin() const + { + return iterator{ + std::string_view{ m_buffer.data(), m_buffer.size() }, + m_lengths, + 0, 0 + }; + } + + [[nodiscard]] iterator end() const + { + return iterator{ + std::string_view{ m_buffer.data(), m_buffer.size() }, + m_lengths, + m_lengths.size(), + m_buffer.size() + }; + } + + [[nodiscard]] const std::vector& buffer() const + { + return m_buffer; + } + + [[nodiscard]] const std::vector& string_lengths() const + { + return m_lengths; + } + + void swap(string_list& other) noexcept + { + m_buffer.swap(other.m_buffer); + m_lengths.swap(other.m_lengths); + } + +private: + std::vector m_buffer{}; + std::vector m_lengths{}; +}; + +} \ No newline at end of file diff --git a/include/util/string_literal.hpp b/include/util/string_literal.hpp new file mode 100644 index 0000000..df896a9 --- /dev/null +++ b/include/util/string_literal.hpp @@ -0,0 +1,734 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ztu { + +#define ztu_ic inline constexpr +#define ztu_nic [[nodiscard]] inline constexpr + +#ifdef __cpp_exceptions +#define ZTU_ASSERT(exp, error) \ + if (not (exp)) [[unlikely]] throw (error); +#else +#include +#include +#define ZTU_TO_STRING_IMPL(x) #x +#define ZTU_TO_STRING(x) ZTU_TO_STRING_IMPL(x) +#define ZTU_ASSERT(exp, error) \ + if (not (exp)) [[unlikely]] { \ + puts(__FILE__ ":" ZTU_TO_STRING(__LINE__) ": Assertion '" ZTU_TO_STRING(exp) "' failed."); \ + abort(); \ + } +#endif + + +using sl_ssize_t = std::make_signed_t; + +template + requires (N > 0) +struct string_literal { + //--------------[ typedefs ]--------------// + + static constexpr auto max_size = N - 1; + using this_type = string_literal; + using array_type = std::array; + using value_type = array_type::value_type; + using size_type = sl_ssize_t; + using difference_type = array_type::difference_type; + using reference = array_type::reference; + using const_reference = array_type::const_reference; + using pointer = array_type::pointer; + using const_pointer = array_type::const_pointer; + using iterator = array_type::iterator; + using const_iterator = array_type::const_iterator; + using reverse_iterator = array_type::reverse_iterator; + using const_reverse_iterator = array_type::const_reverse_iterator; + + + //--------------[ constructors ]--------------// + + ztu_ic string_literal(); + + ztu_ic string_literal(const char (&str)[N]); + + template + requires (M < N) + ztu_ic string_literal(const char (&str)[M]); + + template + requires ( + (1 < sizeof...(Chars) and sizeof...(Chars) < N) and + (std::same_as and ...) + ) + ztu_ic string_literal(Chars... chars); + + ztu_ic string_literal(char c, ssize_t count = 1); + + ztu_ic string_literal(const char* str, std::size_t len); + + ztu_ic string_literal(const std::string &str); + + ztu_ic string_literal(const std::string_view &str); + + + //--------------[ array interface ]--------------// + + ztu_nic reference at(size_type index); + ztu_nic const_reference at(size_type index) const; + + ztu_nic reference operator[](size_type index); + ztu_nic const_reference operator[](size_type index) const; + + ztu_nic reference front(); + ztu_nic const_reference front() const; + + ztu_nic reference back(); + ztu_nic const_reference back() const; + + ztu_nic pointer data() noexcept; + ztu_nic const_pointer data() const noexcept; + + ztu_nic iterator begin() noexcept; + ztu_nic const_iterator begin() const noexcept; + ztu_nic const_iterator cbegin() const noexcept; + + ztu_nic iterator end() noexcept; + ztu_nic const_iterator end() const noexcept; + ztu_nic const_iterator cend() const noexcept; + + ztu_nic reverse_iterator rbegin() noexcept; + ztu_nic const_reverse_iterator rbegin() const noexcept; + ztu_nic const_reverse_iterator rcbegin() const noexcept; + + ztu_nic reverse_iterator rend() noexcept; + ztu_nic const_reverse_iterator rend() const noexcept; + ztu_nic const_reverse_iterator rcend() const noexcept; + + ztu_nic bool empty() const noexcept; + ztu_nic size_type size() const noexcept; + ztu_nic size_type unused_size() const noexcept; + + + //--------------[ string interface ]--------------// + + static constexpr auto max_length = max_size; + + ztu_nic pointer c_str() noexcept; + ztu_nic const_pointer c_str() const noexcept; + + ztu_nic std::string_view view() const; + + ztu_nic size_type length() const noexcept; + + ztu_nic size_type find(char c, ssize_t pos = 0) const; + ztu_nic size_type find(const char *str, size_type len, ssize_t pos) const; + ztu_nic size_type find(const char *str, ssize_t pos = 0) const; + ztu_nic size_type find(const std::string_view &str, ssize_t pos = 0) const; + ztu_nic size_type find(const std::string &str, ssize_t pos = 0) const; + template + ztu_ic size_type find(const string_literal &str) const; + + ztu_ic this_type& resize(size_type new_size, char fill = ' '); + + ztu_ic this_type& erase(const_iterator begin_it, const_iterator end_it); + ztu_ic this_type& erase(size_type index, size_type count = 1); + + ztu_ic this_type& insert(size_type index, char c, size_type repeat = 1); + ztu_ic this_type& insert(size_type index, const char *str); + ztu_ic this_type& insert(size_type index, const char *str, size_type len); + ztu_ic this_type& insert(size_type index, const std::string_view &str); + ztu_ic this_type& insert(size_type index, const std::string &str); + template + ztu_ic this_type& insert(size_type index, const string_literal &str); + + ztu_ic this_type& replace(size_type index, size_type count, char c, size_type repeat = 1); + ztu_ic this_type& replace(size_type index, size_type count, const char *str); + ztu_ic this_type& replace(size_type index, size_type count, const char *str, size_type len); + ztu_ic this_type& replace(size_type index, size_type count, const std::string_view &str); + ztu_ic this_type& replace(size_type index, size_type count, const std::string &str); + template + ztu_ic this_type& replace(size_type index, size_type count, const string_literal &str); + + //--------------[ operators ]--------------// + + ztu_nic bool operator==(const std::string &str) const; + ztu_nic bool operator==(const std::string_view &str) const; + ztu_nic bool operator==(const char* str) const; + template + ztu_nic bool operator==(const string_literal &str) const; + + + template + ztu_ic void assign(const char* str, size_type len); + + ztu_ic this_type& operator=(const std::string &str); + ztu_ic this_type& operator=(const std::string_view &str); + ztu_ic this_type& operator=(const char* str); + template + ztu_ic this_type& operator=(const string_literal &str); + + + ztu_ic void append(const char* str, size_type len); + + ztu_ic this_type& operator+=(const std::string &str); + ztu_ic this_type& operator+=(const std::string_view &str); + ztu_ic this_type& operator+=(const char* str); + template + ztu_ic this_type& operator+=(const string_literal &str); + + + template + ztu_nic string_literal operator+(const string_literal &str) const; + + template + inline friend std::ostream& operator<<(std::ostream &out, const string_literal& str); + + array_type m_value{}; +}; + +//--------------[ predefines ]--------------// + +namespace detail { + template::max()> + ztu_nic sl_ssize_t strlen(const char* str) { + sl_ssize_t len = 0; + while (str[len] != '\0') { + len++; + ZTU_ASSERT(len < MaxLength, std::invalid_argument("given string is not null terminated")); + } + return len; + } +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::size() const noexcept { + return detail::strlen(m_value.data()); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::length() const noexcept { + return this->size(); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::unused_size() const noexcept { + return max_size - this->size(); +} + + +//--------------[ constructors ]--------------// + +template requires (N > 0) +ztu_ic string_literal::string_literal() = default; + +template requires (N > 0) +ztu_ic string_literal::string_literal(const char (&str)[N]) { + std::copy_n(std::begin(str), N, m_value.begin()); + m_value[N - 1] = '\0'; +} + +template requires (N > 0) +template requires (M < N) +ztu_ic string_literal::string_literal(const char (&str)[M]) { + std::copy_n(std::begin(str), M, m_value.begin()); + m_value[M - 1] = '\0'; +} + +template requires (N > 0) +template + requires ( + (1 < sizeof...(Chars) and sizeof...(Chars) < N) and + (std::same_as and ...) + ) +ztu_ic string_literal::string_literal(Chars... chars) + : m_value{ + chars... // default initialization of array elements to 0 terminates string + } {} + +template requires (N > 0) +ztu_ic string_literal::string_literal(const char c, ssize_t count) + : m_value{} +{ + ZTU_ASSERT(count <= max_size, std::length_error("Given count exceeds capacity.")); + std::fill_n(m_value.begin(), count, c); +} + + +template requires (N > 0) +ztu_ic string_literal::string_literal(const char* data, std::size_t size) { + ZTU_ASSERT(size <= max_size, std::length_error("given string exceeds capacity")); + std::copy_n(data, size, m_value.begin()); + m_value[size] = '\0'; +} + +template requires (N > 0) +ztu_ic string_literal::string_literal(const std::string &str) + : string_literal(str.data(), str.size()) {} + +template requires (N > 0) +ztu_ic string_literal::string_literal(const std::string_view &str) + : string_literal(str.data(), str.size()) {} + +//--------------[ array interface ]--------------// + +template requires (N > 0) +ztu_nic string_literal::reference string_literal::at(size_type index) { + const auto m_length = this->length(); + ZTU_ASSERT(0 <= index and index < m_length, std::out_of_range("given index exceeds length")); + return m_value[index]; +} + +template requires (N > 0) +ztu_nic string_literal::const_reference string_literal::at(size_type index) const { + const auto m_length = this->length(); + ZTU_ASSERT(0 <= index and index < m_length, std::out_of_range("given index exceeds length")); + return m_value[index]; +} + +template requires (N > 0) +ztu_nic string_literal::reference string_literal::operator[](size_type index) { + return m_value[index]; +} + +template requires (N > 0) +ztu_nic string_literal::const_reference string_literal::operator[](size_type index) const { + return m_value[index]; +} + +template requires (N > 0) +ztu_nic string_literal::reference string_literal::front() { + return m_value.front(); +} + +template requires (N > 0) +ztu_nic string_literal::const_reference string_literal::front() const { + return m_value.front(); +} + +template requires (N > 0) +ztu_nic string_literal::reference string_literal::back() { + return m_value[this->size() - 1]; +} + +template requires (N > 0) +ztu_nic string_literal::const_reference string_literal::back() const { + return m_value[this->size() - 1]; +} + +template requires (N > 0) +ztu_nic string_literal::pointer string_literal::data() noexcept { + return m_value.data(); +} + +template requires (N > 0) +ztu_nic string_literal::const_pointer string_literal::data() const noexcept { + return m_value.data(); +} + +template requires (N > 0) +ztu_nic string_literal::iterator string_literal::begin() noexcept { + return m_value.begin(); +} + +template requires (N > 0) +ztu_nic string_literal::const_iterator string_literal::begin() const noexcept { + return m_value.begin(); +} + +template requires (N > 0) +ztu_nic string_literal::const_iterator string_literal::cbegin() const noexcept { + return m_value.cbegin(); +} + +template requires (N > 0) +ztu_nic string_literal::iterator string_literal::end() noexcept { + return this->begin() + this->size(); +} + +template requires (N > 0) +ztu_nic string_literal::const_iterator string_literal::end() const noexcept { + return this->begin() + this->size(); +} + +template requires (N > 0) +ztu_nic string_literal::const_iterator string_literal::cend() const noexcept { + return this->begin() + this->size(); +} + +template requires (N > 0) +ztu_nic string_literal::reverse_iterator string_literal::rbegin() noexcept { + return m_value.rbegin() + this->unused_size(); +} + +template requires (N > 0) +ztu_nic string_literal::const_reverse_iterator string_literal::rbegin() const noexcept { + return m_value.rbegin() + this->unused_size(); +} + +template requires (N > 0) +ztu_nic string_literal::const_reverse_iterator string_literal::rcbegin() const noexcept { + return m_value.rcbegin() + this->unused_size(); +} + +template requires (N > 0) +ztu_nic string_literal::reverse_iterator string_literal::rend() noexcept { + return m_value.rend(); +} + +template requires (N > 0) +ztu_nic string_literal::const_reverse_iterator string_literal::rend() const noexcept { + return m_value.rend(); +} + +template requires (N > 0) +ztu_nic string_literal::const_reverse_iterator string_literal::rcend() const noexcept { + return m_value.crend(); +} + +template requires (N > 0) +ztu_nic bool string_literal::empty() const noexcept { + return this->front() == '\0'; +} + + +//--------------[ string interface ]--------------// + +template requires (N > 0) +ztu_nic string_literal::pointer string_literal::c_str() noexcept { + return m_value.data(); +} + +template requires (N > 0) +ztu_nic string_literal::const_pointer string_literal::c_str() const noexcept { + return m_value.data(); +} + +template requires (N > 0) +ztu_nic std::string_view string_literal::view() const { + return { this->c_str() }; +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::find(char c, ssize_t pos) const { + const auto m_length = this->length(); + ZTU_ASSERT(0 <= pos and pos <= m_length, std::out_of_range("Given start pos is out of range.")); + return std::find(this->begin() + pos, this->begin() + m_length, c) - this->begin(); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::find(const char *str, size_type len, ssize_t pos) const { + const auto m_length = this->length(); + ZTU_ASSERT(0 <= pos and pos <= m_length, std::out_of_range("Given start pos is out of range.")); + return std::search(this->begin() + pos, this->begin() + m_length, str, str + len) - this->begin(); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::find(const char *str, ssize_t pos) const { + return this->find(str, detail::strlen(str), pos); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::find(const std::string_view &str, ssize_t pos) const { + return this->find(str.data(), str.length(), pos); +} + +template requires (N > 0) +ztu_nic string_literal::size_type string_literal::find(const std::string &str, ssize_t pos) const { + return this->find(str.data(), str.length(), pos); +} + +template requires (N > 0) +template +ztu_ic string_literal::size_type string_literal::find(const string_literal &str) const { + return this->find(str.c_str(), str.length()); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::resize(size_type new_size, char fill) { + ZTU_ASSERT(0 <= new_size and new_size <= max_size, std::length_error("New size exceeds capacity.")); + m_value[new_size] = '\0'; + if (const auto m_size = this->size(); new_size > m_size) + std::fill_n(this->begin() + m_size, new_size - m_size, fill); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::erase(const_iterator begin_it, const_iterator end_it) { + const auto m_size = this->size(); + const auto m_end = this->begin() + m_size; + ZTU_ASSERT(this->begin() <= begin_it and begin_it <= m_end, std::out_of_range("begin iterator out of range")); + ZTU_ASSERT(this->begin() <= end_it and end_it <= m_end, std::out_of_range("end iterator out of range")); + auto mutable_begin = this->begin() + (begin_it - this->cbegin()); + auto mutable_end = this->begin() + (end_it - this->cbegin()); + const auto right_begin = mutable_end; + const auto right_end = this->begin() + m_size + 1; + std::copy(right_begin, right_end, mutable_begin); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::erase(size_type index, size_type count) { + const auto begin_it = this->begin() + index; + return this->erase(begin_it, begin_it + count); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::insert(size_type index, char c, size_type count) { + const auto m_size = this->size(); + ZTU_ASSERT(0 <= index and index <= m_size, std::out_of_range("given index is out of range")); + ZTU_ASSERT(0 <= count and count <= (max_size - m_size), std::length_error("given sequence exceeds capacity")); + // move right of index with terminator + const auto right_begin = this->begin() + index; + const auto right_end = this->begin() + m_size + 1; + std::copy_backward(right_begin, right_end, right_end + count); + std::fill_n(this->begin() + index, count, c); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::insert(size_type index, const char *str, size_type count) { + const auto m_size = this->size(); + ZTU_ASSERT(0 <= index and index <= m_size, std::out_of_range("given index is out of range")); + ZTU_ASSERT(0 <= count and count <= (max_size - m_size), std::length_error("given sequence exceeds capacity")); + // move right of index with terminator + const auto right_begin = this->begin() + index; + const auto right_end = this->begin() + m_size + 1; + std::copy_backward(right_begin, right_end, right_end + count); + std::copy_n(str, count, this->begin() + index); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::insert(size_type index, const char *str) { + return this->insert(index, str, detail::strlen(str)); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::insert(size_type index, const std::string_view &str) { + return this->insert(index, str.data(), str.length()); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::insert(size_type index, const std::string &str) { + return this->insert(index, str.data(), str.length()); +} + +template requires (N > 0) +template +ztu_ic string_literal::this_type& string_literal::insert(size_type index, const string_literal &str) { + return this->insert(index, str.data(), str.length()); +} + + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, char c, size_type repeat) { + const auto m_size = this->size(); + ZTU_ASSERT(0 <= index and index + count <= m_size, std::out_of_range("given index is out of range")); + ZTU_ASSERT(0 <= count and 0 <= repeat, std::out_of_range("count and repeat must be none negative")); + const auto delta_size = repeat - count; + ZTU_ASSERT((m_size + delta_size) <= max_size, std::length_error("given sequence exceeds capacity")); + + const auto right_begin = this->begin() + index + count; + const auto right_end = this->begin() + m_size + 1; + + if (delta_size < 0) { + std::copy(right_begin, right_end, right_begin + delta_size); + } else if (delta_size > 0) { + std::copy_backward(right_begin, right_end, right_end + delta_size); + } + std::fill_n(this->begin() + index, repeat, c); + + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, const char *str, size_type len) { + const auto m_size = this->size(); + ZTU_ASSERT(0 <= index and index + count <= m_size, std::out_of_range("given index is out of range")); + ZTU_ASSERT(0 <= count and 0 <= len, std::out_of_range("count and len must be none negative")); + const auto delta_size = len - count; + ZTU_ASSERT((m_size + delta_size) <= max_size, std::length_error("given sequence exceeds capacity")); + + const auto right_begin = this->begin() + index + count; + const auto right_end = this->begin() + m_size + 1; + + if (delta_size < 0) { + std::copy(right_begin, right_end, right_begin + delta_size); + } else if (delta_size > 0) { + std::copy_backward(right_begin, right_end, right_end + delta_size); + } + std::copy_n(str, len, this->begin() + index); + + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, const char *str) { + return this->replace(index, count, str, detail::strlen(str)); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, const std::string_view &str) { + return this->replace(index, count, str.data(), str.length()); +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, const std::string &str) { + return this->replace(index, count, str.data(), str.length()); +} + +template requires (N > 0) +template +ztu_ic string_literal::this_type& string_literal::replace(size_type index, size_type count, const string_literal &str) { + return this->replace(index, count, str.data(), str.length()); +} + + +//--------------[ operators ]--------------// + +template requires (N > 0) +ztu_nic bool string_literal::operator==(const std::string &str) const { + const auto o_length = static_cast(str.length()); + for (size_type i = 0; i < o_length; i++) { + if (m_value[i] == '\0' or m_value[i] != str[i]) { + return false; + } + } + return true; +} + +template requires (N > 0) +ztu_nic bool string_literal::operator==(const std::string_view &str) const { + const auto o_length = static_cast(str.length()); + for (size_type i = 0; i < o_length; i++) { + if (m_value[i] == '\0' or m_value[i] != str[i]) { + return false; + } + } + return true; +} + +template requires (N > 0) +ztu_nic bool string_literal::operator==(const char* str) const { + size_type i = 0; + do { + if (i == max_size) [[unlikely]] + return true; + if (m_value[i] != str[i]) + return false; + } while (m_value[i++] != '\0'); + return true; +} + +template requires (N > 0) +template +ztu_nic bool string_literal::operator==(const string_literal &str) const { + return (*this) == str.c_str(); +} + +template requires (N > 0) +template +ztu_ic void string_literal::assign(const char* str, size_type len) { + if constexpr (not KnownToFit) { + ZTU_ASSERT(len <= max_size, std::length_error("given string exceeds capacity")); + } + std::copy_n(str, len, m_value.begin()); + m_value[len] = '\0'; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator=(const std::string &str) { + assign(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator=(const std::string_view &str) { + assign(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator=(const char* str) { + assign(str, detail::strlen(str)); + return *this; +} + +template requires (N > 0) +template +ztu_ic string_literal::this_type& string_literal::operator=(const string_literal &str) { + assign(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +ztu_ic void string_literal::append(const char* str, size_type len) { + const auto m_length = this->length(); + ZTU_ASSERT(len <= (max_size - m_length), std::length_error("given string exceeds available capacity")); + std::copy_n(str, len, this->begin() + m_length); + m_value[m_length + len] = '\0'; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator+=(const std::string &str) { + append(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator+=(const std::string_view &str) { + append(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +ztu_ic string_literal::this_type& string_literal::operator+=(const char* str) { + append(str, detail::strlen(str)); + return *this; +} + +template requires (N > 0) +template +ztu_ic string_literal::this_type& string_literal::operator+=(const string_literal &str) { + append(str.data(), str.length()); + return *this; +} + +template requires (N > 0) +template +ztu_nic string_literal string_literal::operator+(const string_literal &str) const { + string_literal combined{}; + + const auto m_length = this->length(); + const auto o_length = str.length(); + + std::copy_n(this->begin(), m_length, combined.begin()); + std::copy_n(str.begin(), o_length + 1, combined.begin() + m_length); // copy termination + + return combined; +} + +template +inline std::ostream& operator<<(std::ostream &out, const string_literal& str) { + return out << str.c_str(); +} + +namespace string_literals { +template +constexpr auto operator"" _sl() { + return Str; +} +} // string_literals + +#undef ztu_ic +#undef ztu_nic + +} // ztu diff --git a/include/util/string_lookup.hpp b/include/util/string_lookup.hpp new file mode 100644 index 0000000..2064e0a --- /dev/null +++ b/include/util/string_lookup.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace ztu +{ + +namespace detail +{ +struct string_hash +{ + using is_transparent = void; + using hash_type = std::hash; + + [[nodiscard]] std::size_t operator()(const char *txt) const + { + return hash_type{}(txt); + } + [[nodiscard]] std::size_t operator()(std::string_view txt) const + { + return hash_type{}(txt); + } + [[nodiscard]] std::size_t operator()(const std::string &txt) const + { + return hash_type{}(txt); + } +}; +} + +template +using string_lookup = std::unordered_map>; + +} diff --git a/include/util/uix.hpp b/include/util/uix.hpp new file mode 100755 index 0000000..eb337dc --- /dev/null +++ b/include/util/uix.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace ztu { + +namespace uix { + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; +using usize = std::size_t; + +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; +using isize = ssize_t; + +[[maybe_unused]] static constexpr auto u8_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto u16_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto u32_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto u64_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto usize_max = std::numeric_limits::max(); + +[[maybe_unused]] static constexpr auto i8_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto i16_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto i32_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto i64_max = std::numeric_limits::max(); +[[maybe_unused]] static constexpr auto isize_max = std::numeric_limits::max(); + +[[maybe_unused]] static constexpr auto u8_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto u16_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto u32_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto u64_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto usize_min = std::numeric_limits::min(); + +[[maybe_unused]] static constexpr auto i8_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto i16_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto i32_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto i64_min = std::numeric_limits::min(); +[[maybe_unused]] static constexpr auto isize_min = std::numeric_limits::min(); + +} // namespace ztu + +using namespace uix; + +namespace detail { +template +struct pack_at {}; + +template requires (Index > 0) +struct pack_at { + using type = pack_at::type; +}; + +template +struct pack_at<0, T, Ts...> { + using type = T; +}; +} + + +template +using uint_t = detail::pack_at::type; + +template requires (N > 0) +using uint_holding = uint_t<(std::bit_width(static_cast(N)) + 7) / 8>; + + +template +using int_t = detail::pack_at::type; + +template requires (N != 0) +using int_holding = int_t< + std::bit_width( + N < 0 ? + std::max(static_cast(-(N + 1)), static_cast(1)) : + static_cast(N) + ) / 8 + 1 +>; + +} // namespace ztu diff --git a/include/util/unroll_bool_template.hpp b/include/util/unroll_bool_template.hpp new file mode 100644 index 0000000..9d780d5 --- /dev/null +++ b/include/util/unroll_bool_template.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +template +auto unroll_bool_function_template(F&& f) +{ + return f.template operator()(); +} + +template +auto unroll_bool_function_template(F&& f, const Bool b, const Bools... bs) +{ + if (b) + { + return unroll_bool_function_template(std::forward(f), bs...); + } + return unroll_bool_function_template(std::forward(f), bs...); +} diff --git a/include/viewer/asset_loader.hpp b/include/viewer/asset_loader.hpp new file mode 100644 index 0000000..81d0af2 --- /dev/null +++ b/include/viewer/asset_loader.hpp @@ -0,0 +1,195 @@ +#pragma once + +#include +#include +#include + +#include "assets/data_loaders/glsl_loader.hpp" +#include "../assets/dynamic_read_buffers" +#include "opengl/handles/mesh_handle.hpp" +#include "opengl/handles/material_handle.hpp" +#include "opengl/handles/point_cloud_handle.hpp" +#include "SFML/Window.hpp" + +#include "opengl/data/mesh_data.hpp" +#include "opengl/data/material_data.hpp" +#include "opengl/data/point_cloud_data.hpp" +#include "opengl/data/shader_program_data.hpp" + +#include "assets/dynamic_data_loaders/dynamic_mesh_loader.hpp" +#include "assets/data_loaders/obj_loader.hpp" +#include "assets/data_loaders/stl_loader.hpp" + +#include "assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp" +#include "assets/data_loaders/kitti_loader.hpp" +#include "assets/data_loaders/uos_loader.hpp" +#include "assets/data_loaders/uos_normal_loader.hpp" +#include "assets/data_loaders/uos_rgb_loader.hpp" +#include "assets/data_loaders/uosr_loader.hpp" +#include "geometry/aabb.hpp" +#include "opengl/data/shader_data.hpp" + + +namespace viewer +{ + +class asset_loader +{ +public: + using dynamic_mesh_loader_type = dynamic_mesh_loader< + obj_loader, stl_loader + >; + + using dynamic_point_cloud_loader_type = dynamic_point_cloud_loader< + kitti_loader, + uos_loader, + uos_normal_loader, + uos_rgb_loader, + uos_loader, + uosr_loader + >; + + struct dynamic_point_cloud_handle_type + { + zgl::point_cloud_handle handle; + aabb bounding_box; + components::point_cloud_vertex::flags components; + }; + + struct dynamic_material_handle_type + { + zgl::material_handle handle; + material_component::flags components; + }; + + struct dynamic_mesh_handle_type + { + zgl::mesh_handle handle; + aabb bounding_box; + components::mesh_vertex::flags components; + }; + + using material_reference_entry_type = std::pair; + + std::error_code init( + components::mesh_vertex::flags enabled_mesh_components, + material_component::flags enabled_material_components, + components::point_cloud_vertex::flags enabled_point_cloud_components, + const dynamic_material_data& default_material + ); + + std::error_code load_shader( + GLenum type, + const std::filesystem::path& filename, + zgl::shader_handle& shader_handle + ); + + std::error_code build_shader_program( + const zgl::shader_handle& vertex_shader, + const zgl::shader_handle& geometry_shader, + const zgl::shader_handle& fragment_shader, + zgl::shader_program_handle& shader_program_handle + ); + + std::error_code load_asset( + const std::string& format, + const std::filesystem::path& filename, + std::vector>& dynamic_mesh_handles, + std::vector& dynamic_point_cloud_handles + ); + + std::error_code load_asset_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector>& dynamic_mesh_handles, + std::vector& dynamic_point_cloud_handles + ); + + bool unload(const zgl::shader_program_handle& shader_handle); + + void unload_shader_data(); + + bool unload(const zgl::mesh_handle& mesh_handle); + + bool unload(const zgl::point_cloud_handle& point_cloud_handle); + + +protected: + std::error_code load_mesh( + const std::string& format, + const std::filesystem::path& filename, + std::vector>& dynamic_mesh_handles + ); + + std::error_code load_point_cloud( + const std::string& format, + const std::filesystem::path& filename, + std::vector& dynamic_point_cloud_handles + ); + + std::error_code load_mesh_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector>& dynamic_mesh_handles + ); + + std::error_code load_point_cloud_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector& dynamic_point_cloud_handles + ); + + std::error_code process_materials_and_meshes( + std::vector>& dynamic_mesh_handles + ); + + std::error_code process_point_clouds( + std::vector& dynamic_point_cloud_handles + ); + + + std::error_code create_gl_materials(); + + void create_gl_meshes(std::span material_references); + + void create_gl_point_clouds(); + + std::error_code create_gl_shader(); + +private: + //sf::Context m_ctx; + + components::mesh_vertex::flags m_enabled_mesh_components{ + components::mesh_vertex::flags::none + }; + material_component::flags m_enabled_material_components{ + material_component::flags::none + }; + + components::point_cloud_vertex::flags m_enabled_point_cloud_components{ + components::point_cloud_vertex::flags::none + }; + + glsl_loader m_shader_loader{}; + dynamic_mesh_loader_type m_mesh_loader{}; + dynamic_point_cloud_loader_type m_point_cloud_loader{}; + + dynamic_shader_data m_dynamic_shader_data_buffer{}; + std::vector m_dynamic_mesh_data_buffer{}; + std::vector m_dynamic_material_data_buffer{}; + std::vector m_dynamic_point_cloud_buffer{}; + + std::vector m_vertex_buffer{}; + + std::vector m_gl_shader_data{}; + std::vector m_gl_shader_program_data{}; + std::vector> m_gl_mesh_data{}; + std::vector> m_gl_point_cloud_data{}; + std::vector m_gl_material_data{}; + std::vector m_gl_material_data_references{}; + + ztu::u32 next_materials_id{ 0 }; + +}; + +} diff --git a/include/viewer/asset_types.hpp b/include/viewer/asset_types.hpp new file mode 100644 index 0000000..1d6c174 --- /dev/null +++ b/include/viewer/asset_types.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace viewer +{ +enum class asset_types { + mesh, + point_cloud +}; +} diff --git a/include/viewer/dynamic_shader_program_loading.hpp b/include/viewer/dynamic_shader_program_loading.hpp new file mode 100644 index 0000000..e043e30 --- /dev/null +++ b/include/viewer/dynamic_shader_program_loading.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include "asset_loader.hpp" +#include "instance.hpp" + +namespace viewer::dynamic_shader_program_loading +{ + +void load_directory( + asset_loader& loader, + instance& z3d, + std::mutex& gl_resource_lock, + std::mutex& progress_lock, + std::string& progress_title, + float& progress_ratio, + const std::filesystem::path& path +); + +std::size_t count_shader_files( + const std::filesystem::path& path +); + +} diff --git a/include/viewer/instance.hpp b/include/viewer/instance.hpp new file mode 100644 index 0000000..969c96b --- /dev/null +++ b/include/viewer/instance.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +#include "viewer/settings.hpp" +#include + +#include "asset_types.hpp" +#include "geometry/aabb.hpp" +#include "rendering/modes/mesh_modes.hpp" +#include "rendering/modes/point_cloud_modes.hpp" + +#include "scene/flying_camera.hpp" +#include "scene/camera_view.hpp" +#include "scene/lighting_setup.hpp" + +#include "opengl/data/mesh_data.hpp" +#include "opengl/data/material_data.hpp" +#include "opengl/data/point_cloud_data.hpp" +#include "opengl/data/shader_program_data.hpp" +#include "rendering/batch_renderers/mesh_batch_renderer.hpp" +#include "rendering/batch_renderers/point_cloud_batch_renderer.hpp" +#include "rendering/shader_program_lookups/mesh_lookup.hpp" +#include "rendering/shader_program_lookups/point_cloud_lookup.hpp" + +#include "SFML/Graphics/RenderWindow.hpp" +#include "SFML/Graphics/Font.hpp" +#include "SFML/Graphics/Image.hpp" + + +namespace viewer { + +class instance +{ +static constexpr auto id_mesh_index = static_cast(asset_types::mesh); +static constexpr auto id_point_cloud_index = static_cast(asset_types::point_cloud); + +public: + using asset_id = std::variant< + rendering::mesh_batch_renderer::id_type, + rendering::point_cloud_batch_renderer::id_type + >; + + instance(); + + std::error_code init(std::string title); + + std::optional add_mesh( + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + components::mesh_vertex::flags mesh_components, + const zgl::material_handle& material, + material_component::flags material_components + ); + + std::optional add_point_cloud( + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + components::point_cloud_vertex::flags point_cloud_components + ); + + void add_shader_program( + asset_types type, zgl::shader_program_handle shader_program_handle + ); + + void set_background_color(const glm::vec4& color); + + bool remove(asset_id id); + + void run_progress( + std::mutex& lock, + std::string& title, + float& progress, + double fps = 30.0f + ); + + bool look_at(asset_id id); + + void run(std::mutex& gl_resource_lock, double fps = std::numeric_limits::max()); + + void windowed(unsigned int width, unsigned int height, bool decorations = true); + + void fullscreen(); + + void size(unsigned int width, unsigned int height); + +protected: + bool update(double dt); + + void render(); + +public: // TODO fix + sf::ContextSettings m_context_settings; + sf::RenderWindow m_window; + std::string m_title; + glm::ivec2 m_screen_size; + + sf::Font m_font{}; + sf::Image m_logo{}, m_spinner{}; + + rendering::shader_program_lookups::mesh_lookup m_mesh_shader_program_lookup; + rendering::shader_program_lookups::point_cloud_lookup m_point_cloud_shader_program_lookup; + + rendering::mesh_batch_renderer m_mesh_renderer; + rendering::point_cloud_batch_renderer m_point_cloud_renderer; + + rendering::modes::mesh m_mesh_render_mode; + rendering::modes::point_cloud m_point_cloud_render_mode; + + flying_camera m_camera; + camera_view m_view; + + settings m_settings; + bool m_mouse_locked{ false }; +}; + +} // namespace viewer \ No newline at end of file diff --git a/include/viewer/settings.hpp b/include/viewer/settings.hpp new file mode 100644 index 0000000..f297081 --- /dev/null +++ b/include/viewer/settings.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "scene/lighting_setup.hpp" + +namespace viewer +{ +struct settings +{ + float mouse_sensitivity{ 0.001f }; + float scroll_speed{ 0.1f }; + lighting_setup lighting{ + .point_light_direction = { 1, -1, 1 }, + //.point_light_direction = { -1, -1, -1 }, + .point_light_color = { 3.0f, 3.0f, 3.0f }, + .ambient_light_color = { 0.5f, 0.5f, 0.5f } + }; +}; +} diff --git a/main.cpp b/main.cpp new file mode 100755 index 0000000..84c27a4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,431 @@ +#include "viewer/instance.hpp" +#include "util/logger.hpp" +#include +#include +#include +#include + +#include "util/string_lookup.hpp" +#include "viewer/asset_loader.hpp" +#include "viewer/asset_types.hpp" +#include "viewer/dynamic_shader_program_loading.hpp" +#include "util/logger.hpp" +/* +void controller_task( + int args_count, char* args[], + viewer::instance* z3d, + std::mutex* gl_resource_lock, + std::mutex* progress_lock, + std::string* progress_title, + float* progress_ratio +) { + + using namespace std::chrono_literals; + std::this_thread::sleep_for(0.2s); + + using loader_type = viewer::asset_loader; + + auto loader = loader_type{}; + + + auto default_material = dynamic_material_data{}; + std::ignore = default_material.initialized_surface_properties(); // TODO ... + + constexpr auto enabled_mesh_components = ( + components::mesh_vertex::flags::position | + components::mesh_vertex::flags::tex_coord | + components::mesh_vertex::flags::normal | + components::mesh_vertex::flags::color | + components::mesh_vertex::flags::reflectance +); + + constexpr auto enabled_material_components = ( + material_component::flags::texture | + material_component::flags::surface_properties | + material_component::flags::transparency + ); + + // TODO Add point cloud component selection to loaders or remove it all together + constexpr auto enabled_point_cloud_components = ( + components::point_cloud_vertex::flags::position | + components::point_cloud_vertex::flags::normal | + components::point_cloud_vertex::flags::color | + components::point_cloud_vertex::flags::reflectance + ); + if (const auto e = loader.init( + enabled_mesh_components, + enabled_material_components, + enabled_point_cloud_components, + default_material + )) { + ztu::logger::error( + "Error while initializing resource loader: [%] %", + e.category().name(), + e.message() + ); + return; + } + + viewer::dynamic_shader_program_loading::load_directory( + loader, + *z3d, + *gl_resource_lock, + *progress_lock, + *progress_title, + *progress_ratio, + std::filesystem::current_path() / ".." / "shaders" + ); + + progress_lock->lock(); + *progress_title = "Assigning shaders..."; + *progress_ratio = 0.9f; + progress_lock->unlock(); + + progress_lock->lock(); + *progress_title = "Starting..."; + *progress_ratio = 1.0f; + progress_lock->unlock(); + + progress_lock->lock(); + *progress_ratio = std::numeric_limits::max(); + progress_lock->unlock(); + + + std::vector asset_ids; + auto dynamic_mesh_handles = std::vector>{}; + + auto dynamic_point_cloud_handles = std::vector{}; + + for (int i = 1; i + 2 <= args_count; i += 2) + { + + auto format = std::string{ args[i] }; + auto filename = std::filesystem::path{ args[i + 1] }; + + if (const auto e = loader.load_asset( + format, filename, dynamic_mesh_handles, dynamic_point_cloud_handles + )) { + ztu::logger::error( + "Error while loading file %: [%] %", + filename, + e.category().name(), + e.message() + ); + } + + for (const auto& [ dynamic_mesh_handle, dynamic_material_handle ] : dynamic_mesh_handles) + { + const auto& [ mesh_handle, bounding_box, mesh_components ] = dynamic_mesh_handle; + const auto& [ material_handle, material_components ] = dynamic_material_handle; + + ztu::logger::debug("mesh components: %", static_cast(mesh_components)); + + ztu::logger::debug("material components: %", static_cast(material_components)); + + gl_resource_lock->lock(); + const auto asset_id = z3d->add_mesh( + mesh_handle, bounding_box, mesh_components, + material_handle, material_components + ); + gl_resource_lock->unlock(); + ztu::logger::debug("Added mesh to z3d"); + + if (asset_id) + { + asset_ids.push_back(*asset_id); + } + else + { + ztu::logger::warn( + "Ignored mesh as its layout is not supported by z3d." + ); + } + } + + for (const auto& [ point_cloud_handle, bounding_box, point_cloud_components ] : dynamic_point_cloud_handles) + { + gl_resource_lock->lock(); + const auto asset_id = z3d->add_point_cloud( + point_cloud_handle, bounding_box, point_cloud_components + ); + gl_resource_lock->unlock(); + ztu::logger::debug("Added poitn cloud to z3d"); + + if (asset_id) + { + asset_ids.push_back(*asset_id); + } + else + { + ztu::logger::warn( + "Ignored Point cloud, as its layout is not supported by z3d." + ); + } + } + + dynamic_mesh_handles.clear(); + dynamic_point_cloud_handles.clear(); + } + + if (not asset_ids.empty()) + { + // TODO resource lock does not help with update + gl_resource_lock->lock(); + z3d->look_at(asset_ids.front()); + gl_resource_lock->unlock(); + } + + // TODO fix + std::this_thread::sleep_for(20h); +}*/ + + +void controller_task( + int args_count, char* args[], + viewer::instance* z3d, + viewer::asset_loader& loader, + std::mutex* gl_resource_lock, + std::mutex* progress_lock, + std::string* progress_title, + float* progress_ratio +) { + + using namespace std::chrono_literals; + + auto default_material = dynamic_material_data{}; + std::ignore = default_material.initialized_surface_properties(); // TODO ... + + constexpr auto enabled_mesh_components = ( + components::mesh_vertex::flags::position | + components::mesh_vertex::flags::tex_coord | + components::mesh_vertex::flags::normal | + components::mesh_vertex::flags::color | + components::mesh_vertex::flags::reflectance +); + + constexpr auto enabled_material_components = ( + material_component::flags::texture | + material_component::flags::surface_properties | + material_component::flags::transparency + ); + + // TODO Add point cloud component selection to loaders or remove it all together + constexpr auto enabled_point_cloud_components = ( + components::point_cloud_vertex::flags::position | + components::point_cloud_vertex::flags::normal | + components::point_cloud_vertex::flags::color | + components::point_cloud_vertex::flags::reflectance + ); + if (const auto e = loader.init( + enabled_mesh_components, + enabled_material_components, + enabled_point_cloud_components, + default_material + )) { + ztu::logger::error( + "Error while initializing resource loader: [%] %", + e.category().name(), + e.message() + ); + return; + } + + viewer::dynamic_shader_program_loading::load_directory( + loader, + *z3d, + *gl_resource_lock, + *progress_lock, + *progress_title, + *progress_ratio, + std::filesystem::current_path() / ".." / "shaders" + ); + + progress_lock->lock(); + *progress_title = "Assigning shaders..."; + *progress_ratio = 0.9f; + progress_lock->unlock(); + + progress_lock->lock(); + *progress_title = "Starting..."; + *progress_ratio = 1.0f; + progress_lock->unlock(); + + progress_lock->lock(); + *progress_ratio = std::numeric_limits::max(); + progress_lock->unlock(); + + using loader_type = viewer::asset_loader; + + std::vector asset_ids; + auto dynamic_mesh_handles = std::vector>{}; + + auto dynamic_point_cloud_handles = std::vector{}; + + for (int i = 1; i + 2 <= args_count; i += 2) + { + + auto format = std::string{ args[i] }; + auto filename = std::filesystem::path{ args[i + 1] }; + + if (const auto e = loader.load_asset( + format, filename, dynamic_mesh_handles, dynamic_point_cloud_handles + )) { + ztu::logger::error( + "Error while loading file %: [%] %", + filename, + e.category().name(), + e.message() + ); + } + + for (const auto& [ dynamic_mesh_handle, dynamic_material_handle ] : dynamic_mesh_handles) + { + const auto& [ mesh_handle, bounding_box, mesh_components ] = dynamic_mesh_handle; + const auto& [ material_handle, material_components ] = dynamic_material_handle; + + //ztu::logger::debug("mesh components: %", static_cast(mesh_components)); + //ztu::logger::debug("material components: %", static_cast(material_components)); + + gl_resource_lock->lock(); + const auto asset_id = z3d->add_mesh( + mesh_handle, bounding_box, mesh_components, + material_handle, material_components + ); + gl_resource_lock->unlock(); + ztu::logger::debug("Added mesh to z3d"); + + if (asset_id) + { + asset_ids.push_back(*asset_id); + } + else + { + ztu::logger::warn( + "Ignored mesh as its layout is not supported by z3d." + ); + } + } + + for (const auto& [ point_cloud_handle, bounding_box, point_cloud_components ] : dynamic_point_cloud_handles) + { + gl_resource_lock->lock(); + const auto asset_id = z3d->add_point_cloud( + point_cloud_handle, bounding_box, point_cloud_components + ); + gl_resource_lock->unlock(); + ztu::logger::debug("Added poitn cloud to z3d"); + + if (asset_id) + { + asset_ids.push_back(*asset_id); + } + else + { + ztu::logger::warn( + "Ignored Point cloud, as its layout is not supported by z3d." + ); + } + } + + dynamic_mesh_handles.clear(); + dynamic_point_cloud_handles.clear(); + } + + if (not asset_ids.empty()) + { + // TODO resource lock does not help with update + gl_resource_lock->lock(); + z3d->look_at(asset_ids.front()); + gl_resource_lock->unlock(); + } +} + +int main(int args_count, char* args[]) { + + // Clion struggles interleaving the error stream, so we just don't use it... + ztu::logger::global_context().err = &std::cout; + ztu::logger::global_dynamic_config().flags &= ~ztu::logger::flag::time; + + auto z3d = viewer::instance{}; + + if (const auto e = z3d.init("Z3D")) + { + ztu::logger::error( + "Failed to initialize viewer: [%] %", + e.category().name(), + e.message() + ); + return EXIT_FAILURE; + } + + //glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback( + []( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam + ) { + ztu::logger::error("[%] % type: %%, severity: %%, msg: %", + "OpenGL", + (type == GL_DEBUG_TYPE_ERROR ? "ERROR" : ""), + std::hex, type, + std::hex, severity, + message + ); + }, + nullptr + ); + + std::mutex gl_resource_lock; + std::mutex progress_lock; + std::string progress_title = "Initializing..."; + float progress_ratio = 0.0f; + + /*auto controller_thread = std::thread( + controller_task, + args_count, + args, + &z3d, + &gl_resource_lock, + &progress_lock, + &progress_title, + &progress_ratio + );*/ + + auto loader = viewer::asset_loader{}; + + controller_task( + args_count, + args, + &z3d, + loader, + &gl_resource_lock, + &progress_lock, + &progress_title, + &progress_ratio + ); + + z3d.run_progress( + progress_lock, + progress_title, + progress_ratio + ); + + z3d.size(1280, 720); + + + z3d.run(gl_resource_lock, 60.0f); + + + return EXIT_SUCCESS; +} diff --git a/source/assets/data_loaders/generic/generic_3dtk_loader.ipp b/source/assets/data_loaders/generic/generic_3dtk_loader.ipp new file mode 100644 index 0000000..e53ee1b --- /dev/null +++ b/source/assets/data_loaders/generic/generic_3dtk_loader.ipp @@ -0,0 +1,444 @@ +#ifndef INCLUDE_GENERIC_3DTK_LOADER_IMPLEMENTATION +# error Never include this file directly include 'generic_3dtk_loader.hpp' +#endif + +#include +#include +#include "util/logger.hpp" +#include "glm/glm.hpp" +#include "glm/gtx/euler_angles.hpp" +#include +#include + +template +ztu::result generic_3dtk_loader::parse_index( + const std::string_view filename +) { + static constexpr auto prefix = std::string_view{ "scan" }; + + auto name_view = filename.substr(0, name_view.find('.')); + + if (name_view.length() <= prefix.length()) [[unlikely]] + { + return std::make_error_code(std::errc::invalid_argument); + } + + name_view = name_view.substr(prefix.length()); + + pose_prefetch_lookup::index_type index; + + const auto res = std::from_chars(name_view.begin(), name_view.end(), index); + + if (res.ec != std::errc{}) [[unlikely]] + { + return std::make_error_code(res.ec); + } + + return index; +} + +template +std::error_code generic_3dtk_loader::prefetch( + const file_dir_list& paths, + prefetch_queue& queue +) { + + auto path_buffer = std::filesystem::path{}; + + for (const auto filename : paths.files) + { + path_buffer.assign(filename.begin(), filename.end()); + path_buffer.replace_extension(".pose"); + queue.uos_queue.files.push_back(path_buffer.c_str()); + } + + // TODO optimize + for (const auto directory : paths.directories) + { + queue.uos_queue.directories.push_back(directory); + } +} + +template +std::error_code generic_3dtk_loader::load( + dynamic_point_cloud_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& asset_lookup, + dynamic_point_cloud_store& store, + const bool pedantic +) { + namespace fs = std::filesystem; + + auto in = std::ifstream{}; + auto path_buffer = fs::path{}; + auto error = std::error_code{}; + + const auto load_file = [&](const char* filename) + { + // TODO look up pose + auto scan_index = pose_prefetch_lookup::index_type{}; + + if (auto res = parse_index(filename)) + { + scan_index = *res; + } + else [[unlikely]] + { + error = res.error(); + ztu::logger::error( + "Error occurred while parsing scan index in filename %: [%] %", + error.category().name(), + error.message() + ); + } + + auto pose = asset_lookup.poses.find(); + + + const auto id_it = id_lookup.find(filename); + + if (id_it != id_lookup.end()) [[unlikely]] + { + return; + } + + in.open(filename); + if (in.is_open()) + { + if ((error = this->read_point_file(filename, buffer))) { + return error; + } + + this->transform_point_cloud(buffer.positions(), pose); + + const auto id = store.add(std::move(buffer)); + id_lookup.emplace_hint(id_it, filename, id); + } + else + { + ztu::logger::error("Cannot open 3dtk file %", filename); + } + + in.close(); + }; + + for (const auto filename : paths.files) + { + load_file(filename.data()); + } + + + + for (const auto directory : paths.directories) + { + directory_buffer.assign(directory.begin(), directory.end()); + directory_buffer /= "frames"; + + const auto directory_exists = not fs::is_directory(directory_buffer, error); + + if (error or not directory_exists) [[unlikely]] + { + ztu::logger::error("Could not open point cloud directory %", directory_buffer); + continue; + } + + for (const auto& filename : fs::directory_iterator{ directory_buffer }) { + + auto point_filename = reinterpret_cast(filename); + if (point_filename.extension() != ".3d") + { + continue; + } + + load_file(filename.c_str()); + } + } + + return {}; +} + +template +std::error_code generic_3dtk_loader::load_directory( + dynamic_data_loader_ctx& ctx, + dynamic_point_cloud_store& store, + const std::filesystem::path& path, + const bool pedantic +) { + namespace fs = std::filesystem; + + std::error_code error; + + const auto directory_exists = not fs::is_directory(path, error); + if (error) + { + return error; + } + + if (not directory_exists) + { + return make_error_code(std::errc::no_such_file_or_directory); + } + + for (const auto& filename : fs::directory_iterator{ path / "frames" }) { + + auto point_filename = reinterpret_cast(filename); + if (point_filename.extension() != ".3d") { + continue; + } + + if ((error = load(ctx, store, point_filename, pedantic))) + { + ztu::logger::error( + "Error while loading point cloud '%': [%] %", + point_filename, + error.category().name(), + error.message() + ); + } + } + + return {}; +} + +template +std::error_code read_vector(std::string_view& line, std::array& vec) { + for (auto& component : vec) + { + auto it = line.begin(); + + const auto [minus, plus] = std::pair{ *it == '-', *it == '+' }; + it += plus or minus ? 3 : 2; // skip '[-+]?0x' + + const auto [ ptr, ec ] = std::from_chars( + it, line.end(), + component, + std::chars_format::hex + ); + + if (ec != std::errc{}) + { + return std::make_error_code(ec); + } + + if (minus) { + component *= -1.0; + } + + line = { ptr + sizeof(' '), line.end() }; + } + + return {}; +} + +template +std::error_code generic_3dtk_loader::read_point_file( + const std::filesystem::path& filename, + dynamic_point_cloud_data& point_cloud +) { + std::error_code error; + + auto in = std::ifstream(filename); + + if (not in.is_open()) { + return std::make_error_code(static_cast(errno)); + } + + std::string line; + if (not std::getline(in, line)) + { + return std::make_error_code(std::errc::invalid_seek); + } + + constexpr auto expected_component_count = []() + { + auto count = std::tuple_size_v; + + if (Normal) + { + count += std::tuple_size_v; + } + + if (Color) + { + count += std::tuple_size_v; + } + + if (Reflectance) + { + count += std::tuple_size_v; + } + + return count; + }(); + + ztu::u32 component_count; + std::chars_format float_format; + + if ((error = analyze_component_format(line, component_count, float_format))) + { + return error; + } + + if (component_count != expected_component_count) + { + return std::make_error_code(std::errc::invalid_argument); + } + + auto& positions = point_cloud.positions(); + auto& normals = point_cloud.normals(); + auto& colors = point_cloud.colors(); + auto& reflectances = point_cloud.reflectances(); + + do + { + auto line_view = std::string_view{ line }; + + components::point_cloud_vertex::position position; + if ((error = read_vector(line_view, position))) + { + return error; + } + positions.push_back(position); + + if constexpr (Normal) + { + components::point_cloud_vertex::normal normal; + if ((error = read_vector(line_view, normal))) + { + return error; + } + normals.push_back(normal); + } + + if constexpr (Color) + { + components::point_cloud_vertex::color color; + if ((error = read_vector(line_view, color))) + { + return error; + } + colors.push_back(color); + } + + if constexpr (Reflectance) + { + components::point_cloud_vertex::reflectance reflectance; + if ((error = read_vector(line_view, reflectance))) + { + return error; + } + reflectances.push_back(reflectance); + } + } + while (std::getline(in, line)); + + return {}; +} + +std::error_code base_3dtk_loader::read_pose_file( + const std::filesystem::path& filename, + glm::mat4& pose +) { + auto in = std::ifstream(filename); + if (not in.is_open()) { + return std::make_error_code(static_cast(errno)); + } + std::string line; + + std::array numbers{}; + + for (std::size_t row{}; row != 2; ++row) { + + std::getline(in, line); + + auto it = line.cbegin().base(); + auto end = line.cend().base(); + + for (glm::vec3::length_type col{}; col != 3; ++col) { + + const auto [ ptr, ec ] = std::from_chars( + it, end, + numbers[row][col], + std::chars_format::general + ); + + if (ec != std::errc{}) { + return std::make_error_code(ec); + } + + it = ptr + 1; // skip space in between components + } + } + + const auto& translation = numbers[0]; + auto& angles = numbers[1]; + angles *= static_cast(M_PI / 180.0); + + pose = ( + glm::translate(glm::identity(), translation) * + glm::eulerAngleXYZ(angles[0], angles[1], angles[2]) + ); + + return {}; +} + +std::error_code base_3dtk_loader::analyze_component_format( + const std::string& line, + ztu::u32& component_count, + std::chars_format& format +) { + auto begin = line.cbegin().base(); + auto end = line.cend().base(); + + format = std::chars_format::general; + + component_count = 0; + float buffer; + + for (auto it = begin; it < end; it += sizeof(' ')) + { + it += *it == '-' or *it == '+'; + + std::chars_format current_format; + if (*it == '0' and std::next(it) < end and *std::next(it) == 'x') + { + it += 2; // skip '0x' + current_format = std::chars_format::hex; + } + else + { + current_format = std::chars_format::general; + } + + if (it == begin and current_format != format) + { + return std::make_error_code(std::errc::invalid_argument); + } + + const auto [next_it, err] = std::from_chars(it, end, buffer, current_format); + if (err != std::errc()) + { + return std::make_error_code(err); + } + + it = next_it; + format = current_format; + ++component_count; + } + + return {}; +} + + +void base_3dtk_loader::transform_point_cloud( + std::span points, + const glm::mat4& pose +) { + for (auto& [ x, y, z ] : points) { + auto vec = glm::vec4{ x, y, z, 1.0f }; + vec = pose * vec; + x = vec.x; + y = vec.y; + z = vec.z; + } +} diff --git a/source/assets/data_loaders/glsl_loader.cpp b/source/assets/data_loaders/glsl_loader.cpp new file mode 100644 index 0000000..43cbe31 --- /dev/null +++ b/source/assets/data_loaders/glsl_loader.cpp @@ -0,0 +1,36 @@ +#include "assets/data_loaders/glsl_loader.hpp" + +#include + +std::error_code glsl_loader::load( + const std::filesystem::path& filename, + std::string& source +) { + + auto file = std::ifstream(filename); + if (not file.is_open()) + { + return std::make_error_code(std::errc::no_such_file_or_directory); + } + + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + + if (size == 0 or size == std::numeric_limits::max()) + { + return std::make_error_code(std::errc::invalid_seek); + } + + source.reserve(size); + + file.seekg(0, std::ios::beg); + + source.assign( + std::istreambuf_iterator(file), + std::istreambuf_iterator() + ); + + file.close(); + + return {}; +} diff --git a/source/assets/data_loaders/kitti_loader.cpp b/source/assets/data_loaders/kitti_loader.cpp new file mode 100644 index 0000000..71b73a9 --- /dev/null +++ b/source/assets/data_loaders/kitti_loader.cpp @@ -0,0 +1,302 @@ +#include "assets/data_loaders/kitti_loader.hpp" + +#include "glm/glm.hpp" + +#include +#include +#include + +#include "assets/components/point_cloud_vertex_components.hpp" +#include "util/binary_ifstream.hpp" +#include "util/logger.hpp" + + +ztu::result kitti_loader::parent_directory(const std::string_view path) +{ + const auto sep_index = path.rfind(std::filesystem::path::preferred_separator); + + if (sep_index == std::string_view::npos) + { + return std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory)); + } + + return path.substr(0, sep_index); +}; + +std::error_code kitti_loader::prefetch( + const file_dir_list& paths, + prefetch_queue& queue +) { + + // Directories can simply be passed on + queue.kitti_pose_queue.directories.push_back(paths.directories); + + // For files, we just forward the files directory + for (const auto file : queue.kitti_pose_queue.files) + { + if (const auto base_directory = parent_directory(file).and_then(parent_directory)) + { + queue.kitti_pose_queue.directories.push_back(*base_directory); + } + else + { + // TODO remove from list + ztu::logger::error("Malformed kitti file path: %.", file); + } + } + + return {}; +} + +std::error_code kitti_loader::load( + dynamic_point_cloud_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool +) { + namespace fs = std::filesystem; + std::error_code error; + + std::vector pose_its; + pose_its.reserve(paths.files.size()); + + auto processed_filenames = ztu::string_list{}; + + auto path_buffer = fs::path{}; + + const auto preprocess_filename = [&]( + std::string_view path, + const auto& directory, + std::string_view filename, + const pose_prefetch_lookup::directory_iterator& dir_it + ) { + const auto pose_index = frame_id_from_filename(filename); + if (not pose_index) [[unlikely]] + { + ztu::logger::error("Could not parse frame id from kitti file path: %.", filename); + return; + } + + const auto [ index_it, pose_id_match ] = id_lookup.poses.find_index(dir_it, *pose_index); + + if (not pose_id_match) [[unlikely]] + { + ztu::logger::error("No matching pose index (%) found in directory (%).", directory, *pose_index); + return; + } + + const auto [ pose_it, pose_match ] = store.poses.find(pose_id_match); + if (not pose_id_match) [[unlikely]] + { + ztu::logger::error("No matching pose found for id: %.", pose_id_match); + return; + } + + processed_filenames.push_back(path); + pose_its.push_back(pose_it); + }; + + for (const auto file : paths.files) + { + path_buffer.assign(file.begin(), file.end()); + + if (not fs::is_regular_file(path_buffer)) + { + ztu::logger::error("Given kitti file does not exist: %.", path_buffer); + continue; + } + + const auto sep_index = file.rfind(fs::path::preferred_separator); + + if (sep_index == std::string_view::npos) [[unlikely]] + { + ztu::logger::error("Could not parse frame directory from kitti file path: %.", file); + continue; + } + + const auto base_directory = parent_directory(file.substr(0, sep_index)); + if (not base_directory) [[unlikely]] + { + ztu::logger::error("Could not parse base directory from kitti file path: %.", file); + continue; + } + + const auto [ dir_it, dir_match ] = id_lookup.poses.find_directory(*base_directory); + if (not dir_match) [[unlikely]] + { + ztu::logger::error("No matching pose directory found for %.", file); + continue; + } + + const auto filename = file.substr(sep_index + 1); + + preprocess_filename( + file, + *base_directory, + filename, + dir_it + ); + } + + for (const auto directory : paths.directories) + { + path_buffer.assign(directory.begin(), directory.end()); + + const auto [ dir_it, dir_match ] = id_lookup.poses.find_directory(path_buffer); + if (not dir_match) [[unlikely]] + { + ztu::logger::error("No matching pose directory found for %.", path_buffer); + continue; + } + + path_buffer /= frame_folder; + + if (not fs::is_directory(path_buffer)) + { + ztu::logger::error("Given kitti directory does not exist: %.", directory); + continue; + } + + for (const auto& file : fs::directory_iterator{ path_buffer }) + { + const auto file_path = std::string_view{ file.path().c_str() }; + + const auto extension_begin = file_path.rfind('.'); + + if (extension_begin == std::string_view::npos or file_path.substr(extension_begin) != ".bin") + { + continue; + } + + auto filename_begin = file_path.rfind(fs::path::preferred_separator, extension_begin); + filename_begin = filename_begin == std::string_view::npos ? 0 : filename_begin + 1; + + const auto filename_only = file_path.substr(filename_begin); + + const auto pose_index = frame_id_from_filename(filename_only); + if (not pose_index) [[unlikely]] + { + ztu::logger::error("Could not parse frame id from kitti filename: %.", filename_only); + continue; + } + + const auto [ index_it, pose_id_match ] = id_lookup.poses.find_index(dir_it, *pose_index); + + if (not pose_id_match) [[unlikely]] + { + ztu::logger::error("No matching pose index (%) found in directory (%).", directory, *pose_index); + continue; + } + + const auto [ pose_it, pose_match ] = store.poses.find(pose_id_match); + if (not pose_id_match) [[unlikely]] + { + ztu::logger::error("No matching pose found for id: %.", pose_id_match); + continue; + } + + processed_filenames.push_back(file_path); + pose_its.push_back(pose_it); + + preprocess_filename( + file_path, + directory, + filename_only, + dir_it + ); + } + } + + + for (const auto [ filename, pose_it ] : std::ranges::views::zip_view(processed_filenames, pose_its)) + { + buffer.clear(); + + if ((error = load_point_file(filename, buffer))) + { + return error; + } + + transform_point_cloud(buffer.positions(), *pose_it); + + store.point_clouds.add(buffer); + } + + return {}; +} + +void kitti_loader::transform_point_cloud( + std::span points, + const glm::mat4& pose +) { + for (auto& [ x, y, z ] : points) { + auto vec = glm::vec4{ x, y, z, 1.0f }; + vec = pose * vec; + x = vec.x; + y = vec.y; + z = vec.z; + } +} + +std::error_code kitti_loader::load_point_file( + const std::filesystem::path& filename, + dynamic_point_cloud_buffer& point_cloud +) { + + auto in = binary_ifstream{}; + + auto error = std::error_code{}; + + if ((error == in.open(filename, true))) + { + return error; + } + + const auto read_vector = [&in](auto& vector) -> std::error_code + { + for (auto& component : vector) + { + float component32; + if (const auto e = in.read_ieee754(component32)) + { + return e; + } + component = component32; + } + return {}; + }; + + components::point_cloud_vertex::position position; + + auto& positions = point_cloud.positions(); + + while (not ((error = read_vector(position)))) { + positions.push_back(position); + if ((error = in.skip())) // TODO what am I skipping here?!? + { + break; + } + } + + if (static_cast(error.value()) != std::errc::result_out_of_range) + { + return error; + } + + return {}; +} + +ztu::result kitti_loader::frame_id_from_filename( + std::string_view filename +) { + std::size_t id; + const auto result = std::from_chars(filename.cbegin(), filename.cend(), id); + + if (result.ec != std::errc{}) + { + return std::unexpected(std::make_error_code(result.ec)); + } + + return id; +} diff --git a/source/assets/data_loaders/kitti_pose_loader.cpp b/source/assets/data_loaders/kitti_pose_loader.cpp new file mode 100644 index 0000000..d256b8a --- /dev/null +++ b/source/assets/data_loaders/kitti_pose_loader.cpp @@ -0,0 +1,163 @@ +#include "assets/data_loaders/kitti_pose_loader.hpp" + +#include "assets/dynamic_read_buffers/dynamic_pose_buffer.hpp" +#include +#include +#include "util/logger.hpp" + +inline std::error_code kitti_pose_loader::parse_pose( + std::ifstream& in, + dynamic_pose_buffer& pose +) { + for (dynamic_pose_buffer::length_type row{}; row != 3; ++row) + { + for (dynamic_pose_buffer::length_type col{}; col != 4; ++col) + { + if (not (in >> pose[row][col])) + { + return std::make_error_code(std::errc::result_out_of_range); + } + } + } + + return {}; +} + + +std::error_code kitti_pose_loader::prefetch( + const file_dir_list& paths, + prefetch_queue& queue +) { + // Nothing to be done here +} + +std::error_code kitti_pose_loader::load( + dynamic_pose_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& id_lookup, + dynamic_data_store& store, + bool pedantic +) { + namespace fs = std::filesystem; + + auto path_buffer = fs::path{}; + auto in = std::ifstream{}; // TODO disable exceptions (for other loaders as well) + auto pose_buffer = dynamic_pose_buffer{}; + + pose_buffer = glm::identity(); + + auto processed_filenames = ztu::string_list{}; + + processed_filenames.reserve( + paths.files.character_count() + + paths.directories.character_count() + + paths.directories.size() * pose_filename.size(), + paths.files.size() + paths.directories.size() + ); + + const auto preprocess_file = [&]() + { + if (not fs::is_regular_file(path_buffer)) + { + ztu::logger::error("Kitti pose file does not exist: %", path_buffer); + return; + } + + processed_filenames.push_back(path_buffer.c_str()); + }; + + for (const auto directory : paths.directories) { + path_buffer.assign(directory.begin(), directory.end()); + path_buffer /= "pose.txt"; + preprocess_file(); + } + + for (const auto file : paths.files) { + path_buffer.assign(file.begin(), file.end()); + preprocess_file(); + } + + for (const auto filename : processed_filenames) + { + in.open(filename.data()); // Safe because string list adds null terminator + if (not in.is_open()) + { + ztu::logger::error("Cannot open kitti pose file %", path_buffer); + continue; + } + + in >> std::skipws; + + for (auto i = pose_prefetch_lookup::index_type{}; in.peek() != std::ifstream::traits_type::eof(); ++i) + { + if (const auto error = parse_pose(in, pose_buffer)) + { + ztu::logger::error( + "Error occurred while parsing kitti pose % in file %: [%] %", + i, + path_buffer, + error.category().name(), + error.message() + ); + continue; + } + + const auto id = store.poses.add(pose_buffer); + + // TODO if (not) removing the path separator creates issues. + const auto directory = filename.substr(0, filename.length() - pose_filename.length()); + + id_lookup.poses.emplace(directory, i, id); + } + + in.close(); + } +} + +void kitti_pose_loader::load( + const ztu::string_list& directories, + dynamic_pose_store& store, + pose_prefetch_lookup& id_lookup +) { + + auto filename_buffer = std::filesystem::path{}; + auto in = std::ifstream{}; // TODO disable exceptions (for other loaders as well) + auto pose_buffer = dynamic_pose_buffer{}; + + pose_buffer = glm::identity(); + + for (const auto directory : directories) + { + filename_buffer = directory; + filename_buffer /= "pose.txt"; + + in.open(filename_buffer); + if (not in.is_open()) + { + ztu::logger::error("Cannot open kitti pose file %", filename_buffer); + continue; + } + + in >> std::skipws; + + for (auto i = pose_prefetch_lookup::index_type{}; in.peek() != std::ifstream::traits_type::eof(); ++i) + { + if (const auto error = parse_pose(in, pose_buffer)) + { + ztu::logger::error( + "Error occurred while parsing kitti pose % in file %: [%] %", + i, + filename_buffer, + error.category().name(), + error.message() + ); + continue; + } + + const auto id = store.add(pose_buffer); + id_lookup.emplace(directory, i, id); + } + + in.close(); + } +} \ No newline at end of file diff --git a/source/assets/data_loaders/mtl_loader.cpp b/source/assets/data_loaders/mtl_loader.cpp new file mode 100644 index 0000000..040d411 --- /dev/null +++ b/source/assets/data_loaders/mtl_loader.cpp @@ -0,0 +1,398 @@ +#include "assets/data_loaders/mtl_loader.hpp" + +#include +#include +#include + +#include "util/logger.hpp" +#include "util/for_each.hpp" +#include "util/line_parser.hpp" + +#include "assets/dynamic_data_loaders/dynamic_texture_loader.hpp" + +namespace mtl_loader_error { + +struct category : std::error_category { + [[nodiscard]] const char* name() const noexcept override { + return "connector"; + } + + [[nodiscard]] std::string message(int ev) const override { + switch (static_cast(ev)) { + using enum codes; + case mtl_cannot_open_file: + return "Cannot open mtl file."; + case mtl_cannot_open_texture: + return "Cannot open texture file."; + case mtl_malformed_ambient_color: + return "File contains malformed 'Ka' statement."; + case mtl_malformed_diffuse_color: + return "File contains malformed 'Kd' statement."; + case mtl_malformed_specular_color: + return "File contains malformed 'Ks' statement."; + case mtl_malformed_specular_exponent: + return "File contains malformed 'Ns' statement."; + case mtl_malformed_dissolve: + return "File contains malformed 'd' statement."; + case mlt_unknown_line_begin: + return "Unknown mtl line begin"; + default: + using namespace std::string_literals; + return "unrecognized error ("s + std::to_string(ev) + ")"; + } + } +}; + +} // namespace mesh_loader_error + +inline std::error_category& connector_error_category() { + static mtl_loader_error::category category; + return category; +} + +namespace mtl_loader_error { + +inline std::error_code make_error_code(codes e) { + return { static_cast(e), connector_error_category() }; +} + +} // namespace mtl_loader_error + + +template +std::errc parse_numeric_vector(std::string_view param, std::array& values) { + auto it = param.begin(), end = param.end(); + + for (auto& value : values) + { + if (it >= end) + { + return std::errc::invalid_argument; + } + + const auto [ptr, ec] = std::from_chars(it, end, value); + + if (ec != std::errc{}) + { + return ec; + } + + it = ptr + 1; // skip space in between components + } + + return {}; +}; + +std::optional mtl_loader::find_id(std::string_view name) +{ + const auto it = m_id_lookup.find(name); + + if (it == m_id_lookup.end()) + { + return it->second; + } + + return std::nullopt; +} + +void mtl_loader::clear_name_lookup() { + m_id_lookup.clear(); +} + +std::error_code mtl_loader::load_directory( + dynamic_data_loader_ctx& ctx, + dynamic_material_store& store, + components::material::flags enabled_components, + const std::filesystem::path& path, + const bool pedantic +) { + namespace fs = std::filesystem; + + if (not fs::exists(path)) + { + return make_error_code(std::errc::no_such_file_or_directory); + } + + for (const auto& file : fs::directory_iterator{ path }) + { + const auto& file_path = file.path(); + + if (file_path.extension() != ".obj") + { + continue; + } + + if (const auto e = load( + ctx, + store, + enabled_components, + path, + pedantic + )) { + ztu::logger::error( + "Error while loading obj file '%': [%] %", + file_path, + e.category().name(), + e.message() + ); + } + } + + return {}; +} + +std::error_code mtl_loader::load( + dynamic_data_loader_ctx& ctx, + dynamic_material_store& store, + components::material::flags enabled_components, + const std::filesystem::path& filename, + const bool pedantic +) { + using mtl_loader_error::codes; + using mtl_loader_error::make_error_code; + + using flags = components::material::flags; + + const auto component_disabled = [&](const components::material::flags component) { + return (enabled_components & component) == flags::none; + }; + + // TODO unroll stuff + const auto textures_disabled = component_disabled(flags::ambient_filter_texture); + const auto surface_properties_disabled = component_disabled(flags::surface_properties); + const auto transparencies_disabled = component_disabled(flags::transparency); + + auto in = std::ifstream{ filename }; + if (not in.is_open()) { + return make_error_code(codes::mtl_cannot_open_file); + } + + namespace fs = std::filesystem; + const auto directory = fs::canonical(fs::path(filename).parent_path()); + + auto name = std::string{}; + auto material = dynamic_material_data{}; + + const auto push_material = [&]() + { + if (not name.empty()) + { + const auto id = store.add(std::move(material)); + m_id_lookup.emplace(std::move(name), id); + } + name = std::string{}; + material = dynamic_material_data{}; + }; + + const auto load_texture = [&]( + const std::string_view path, + std::string_view texture_type_name, + auto&& f + ) { + auto texture_filename = fs::path(path); + if (texture_filename.is_relative()) + { + texture_filename = directory / texture_filename; + } + + const auto extension = texture_filename.extension().string(); + + auto texture_type = std::string_view{ extension }; + if (not texture_type.empty() and texture_type.front() == '.') + { + texture_type = texture_type.substr(1); + } + + if (const auto loader_id = ctx.texture_loader.find_loader(texture_type)) + { + if (auto res = ctx.texture_loader.read( + ctx, + *loader_id, + texture_filename, + pedantic + )) { + f(*res); + } + else + { + const auto error = res.error(); + ztu::logger::warn( + "Error while loading % texture '%': [%] %", + texture_type_name, + path, + error.category().name(), + error.message() + ); + } + } + else + { + ztu::logger::warn( + "Failed to load % texture '%' because extension is not supported.", + texture_type_name, + path + ); + } + }; + + const auto ec = ztu::parse_lines( + in, + pedantic, + ztu::make_line_parser("newmtl ", ztu::is_not_repeating, [&](const auto& param) + { + push_material(); + name = param; + + return codes::ok; + }), + ztu::make_line_parser("Ka ", ztu::is_not_repeating, [&](const auto& param) + { + if (surface_properties_disabled) return codes::ok; + + auto& properties = material.initialized_surface_properties(); + if (parse_numeric_vector(param, properties.ambient_filter) != std::errc{}) [[unlikely]] + { + return codes::mtl_malformed_ambient_color; + } + + material.components() |= flags::surface_properties; + + return codes::ok; + }), + ztu::make_line_parser("Kd ", ztu::is_not_repeating, [&](const auto& param) + { + if (surface_properties_disabled) return codes::ok; + + auto& properties = material.initialized_surface_properties(); + if (parse_numeric_vector(param, properties.diffuse_filter) != std::errc{}) [[unlikely]] + { + return codes::mtl_malformed_diffuse_color; + } + + material.components() |= flags::surface_properties; + + return codes::ok; + }), + ztu::make_line_parser("Ks ", ztu::is_not_repeating, [&](const auto& param) + { + if (surface_properties_disabled) return codes::ok; + + auto& properties = material.initialized_surface_properties(); + if (parse_numeric_vector(param, properties.specular_filter) != std::errc{}) [[unlikely]] + { + return codes::mtl_malformed_specular_color; + } + + material.components() |= flags::surface_properties; + + return codes::ok; + }), + ztu::make_line_parser("Ns ", ztu::is_not_repeating, [&](const auto& param) + { + if (surface_properties_disabled) return codes::ok; + + auto& properties = material.initialized_surface_properties(); + std::array shininess{}; + if (parse_numeric_vector(param, shininess) != std::errc{}) [[unlikely]] + { + return codes::mtl_malformed_specular_exponent; + } + + properties.shininess = shininess.front(); + material.components() |= flags::surface_properties; + + return codes::ok; + }), + ztu::make_line_parser("d ", ztu::is_not_repeating, [&](const auto& param) + { + if (transparencies_disabled) return codes::ok; + + std::array transparency{}; + if (parse_numeric_vector(param, transparency) != std::errc{}) [[unlikely]] + { + return codes::mtl_malformed_dissolve; + } + + material.transparency().emplace(transparency.front()); + material.components() |= flags::transparency; + + return codes::ok; + }), + ztu::make_line_parser("map_Ka ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "ambient color", [&](const auto id) { + material.ambient_color_texture_id() = id; + material.components() |= flags::ambient_filter_texture; + }); + + return codes::ok; + }), + ztu::make_line_parser("map_Kd ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "diffuse color", [&](const auto id) { + material.diffuse_color_texture_id() = id; + material.components() |= flags::diffuse_filter_texture; + }); + + return codes::ok; + }), + ztu::make_line_parser("map_Ks ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "specular color", [&](const auto id) { + material.specular_color_texture_id() = id; + material.components() |= flags::specular_filter_texture; + }); + + return codes::ok; + }), + ztu::make_line_parser("map_Ns ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "shininess", [&](const auto id) { + material.shininess_texture_id() = id; + material.components() |= flags::shininess_texture; + }); + + return codes::ok; + }), + ztu::make_line_parser("map_d ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "alpha", [&](const auto id) { + material.alpha_texture_id() = id; + material.components() |= flags::alpha_texture; + }); + + return codes::ok; + }), + ztu::make_line_parser("bump ", ztu::is_not_repeating, [&](const auto& param) + { + if (textures_disabled) return codes::ok; + + load_texture(param, "bump", [&](const auto id) { + material.bump_texture_id() = id; + material.components() |= flags::bump_texture; + }); + + return codes::ok; + }) + ); + + if (ec != codes::ok) + { + return make_error_code(ec); + } + + push_material(); + + return {}; +} + diff --git a/source/assets/data_loaders/obj_loader.cpp b/source/assets/data_loaders/obj_loader.cpp new file mode 100755 index 0000000..b15b2cd --- /dev/null +++ b/source/assets/data_loaders/obj_loader.cpp @@ -0,0 +1,450 @@ +#include "assets/data_loaders/obj_loader.hpp" + +#include +#include +#include + +#include "assets/components/mesh_vertex_components.hpp" +#include "assets/dynamic_data_loaders/dynamic_material_loader.hpp" + +#include "util/logger.hpp" +#include "util/for_each.hpp" +#include "util/uix.hpp" +#include + +#include "util/line_parser.hpp" + +namespace obj_loader_error { + +struct category : std::error_category { + [[nodiscard]] const char* name() const noexcept override { + return "connector"; + } + + [[nodiscard]] std::string message(int ev) const override { + switch (static_cast(ev)) { + using enum codes; + case obj_cannot_open_file: + return "Cannot open given obj file."; + case obj_malformed_vertex: + return "File contains malformed 'v' statement."; + case obj_malformed_texture_coordinate: + return "File contains malformed 'vt' statement."; + case obj_malformed_normal: + return "File contains malformed 'vn' statement."; + case obj_malformed_face: + return "File contains malformed 'f' statement."; + case obj_face_index_out_of_range: + return "Face index out of range."; + case obj_unknown_line_begin: + return "Unknown obj line begin."; + default: + using namespace std::string_literals; + return "unrecognized error ("s + std::to_string(ev) + ")"; + } + } +}; + +} // namespace mesh_loader_error + +inline std::error_category& connector_error_category() { + static obj_loader_error::category category; + return category; +} + +namespace obj_loader_error { + +inline std::error_code make_error_code(codes e) { + return { static_cast(e), connector_error_category() }; +} + +} // namespace mesh_loader_error + + +using vertex_type = std::array; + +struct indexed_vertex_type { + vertex_type vertex; + ztu::u32 buffer_index; + + friend auto operator<=>(const indexed_vertex_type& a, const indexed_vertex_type& b) { + return a.vertex <=> b.vertex; + } + + bool operator==(const indexed_vertex_type& other) const noexcept { + return other.vertex == vertex; + } +}; + + +// TODO add compile time selection and unrolling +template +std::errc parse_numeric_vector(std::string_view param, std::array& values) { + auto it = param.begin(), end = param.end(); + + for (auto& value : values) + { + if (it >= end) + { + return std::errc::invalid_argument; + } + + const auto [ptr, ec] = std::from_chars(it, end, value); + + if (ec != std::errc{}) + { + return ec; + } + + it = ptr + 1; // skip space in between components + } + + return {}; +}; + +std::error_code obj_loader::load_directory( + dynamic_data_loader_ctx& ctx, + dynamic_mesh_store& store, + components::mesh_vertex::flags enabled_components, + const std::filesystem::path& path, + const bool pedantic +) { + namespace fs = std::filesystem; + + if (not fs::exists(path)) + { + return make_error_code(std::errc::no_such_file_or_directory); + } + + for (const auto& file : fs::directory_iterator{ path }) + { + const auto& file_path = file.path(); + + if (file_path.extension() != ".obj") + { + continue; + } + + if (const auto e = load( + ctx, + store, + enabled_components, + path, + pedantic + )) { + ztu::logger::error( + "Error while loading obj file '%': [%] %", + file_path, + e.category().name(), + e.message() + ); + } + } + + return {}; +} + + +// TODO refactor so there is a function like parse_normals etc. + +std::error_code obj_loader::load( + dynamic_data_loader_ctx& ctx, + dynamic_mesh_store& store, + components::mesh_vertex::flags enabled_components, + const std::filesystem::path& filename, + const bool pedantic +) { + using obj_loader_error::codes; + using obj_loader_error::make_error_code; + + auto in = std::ifstream{ filename }; + if (not in.is_open()) { + return make_error_code(codes::obj_cannot_open_file); + } + + namespace fs = std::filesystem; + const auto directory = fs::path(filename).parent_path(); + + // Each vertex of a face can represent a unique combination of vertex-/texture-/normal-coordinates. + // But some combinations may occur more than once, for example on every corner of a cube 3 triangles will + // reference the exact same corner vertex. + // To get the best rendering performance and lowest final memory footprint these duplicates + // need to be removed. So this std::set lookup is used to identify the aforementioned duplicates + // and only push unique combinations to the buffers. + std::set vertex_ids; + + auto mesh = dynamic_mesh_data{}; + + // Buffers + auto position_buffer = mesh.positions(); + auto normal_buffer = mesh.normals(); + auto tex_coord_buffer = mesh.tex_coords(); + + std::unordered_map material_name_lookup; + + + constexpr auto mtl_loader_id = *ctx.material_loader.find_loader_static("mtl"); + mtl_loader& material_loader = ctx.material_loader.get_loader(); + + material_loader.clear_name_lookup(); + + std::string material_name; + + const auto push_mesh = [&](const bool clear_buffers = false) { + + if (not mesh.positions().empty()) + { + // Copy buffers instead of moving to keep capacity for further parsing + // and have the final buffers be shrunk to size. + if (not material_name.empty()) { + if (const auto id = material_loader.find_id(material_name)) + { + mesh.material_id() = *id; + } + else + { + ztu::logger::warn( + "Could not find material '%'.", + material_name + ); + } + } + + ztu::logger::debug("Parsed % positions.", mesh.positions().size()); + ztu::logger::debug("Parsed % normals.", mesh.normals().size()); + ztu::logger::debug("Parsed % tex_coords.", mesh.tex_coords().size()); + + if (not mesh.positions().empty()) + { + mesh.components() |= components::mesh_vertex::flags::position; + } + + if (not mesh.normals().empty()) + { + mesh.components() |= components::mesh_vertex::flags::normal; + } + + if (not mesh.tex_coords().empty()) + { + mesh.components() |= components::mesh_vertex::flags::tex_coord; + } + + ztu::logger::debug("Pushing obj mesh with % triangles.", mesh.triangles().size()); + + store.add(std::move(mesh)); + } + + if (clear_buffers) + { + position_buffer.clear(); + normal_buffer.clear(); + tex_coord_buffer.clear(); + } + + mesh = dynamic_mesh_data{}; + + vertex_ids.clear(); + material_name.clear(); + }; + + const auto find_or_push_vertex = [&](const vertex_type& vertex) -> ztu::u32 { + + auto indexed_vid = indexed_vertex_type{ + .vertex = vertex, + .buffer_index = static_cast(mesh.positions().size()) + }; + + // Search through sorted lookup to check if index combination is unique + const auto [ id_it, unique ] = vertex_ids.insert(indexed_vid); + + if (unique) + { + const auto& [ position_index, tex_coord_index, normal_index ] = vertex; + + if (position_index < position_buffer.size()) + { + mesh.positions().emplace_back(position_buffer[position_index]); + } + + if (normal_index < normal_buffer.size()) + { + mesh.normals().emplace_back(normal_buffer[normal_index]); + } + + if (tex_coord_index < tex_coord_buffer.size()) + { + mesh.tex_coords().emplace_back(tex_coord_buffer[tex_coord_index]); + } + } + + return id_it->buffer_index; + }; + + using flags = components::mesh_vertex::flags; + + const auto component_disabled = [&](const flags component) { + return (enabled_components & component) == flags::none; + }; + + const auto positions_disabled = component_disabled(flags::position); + const auto normals_disabled = component_disabled(flags::normal); + const auto tex_coords_disabled = component_disabled(flags::tex_coord); + + const auto ec = ztu::parse_lines( + in, + pedantic, + ztu::make_line_parser("v ", ztu::is_repeating, [&](const auto& param) + { + if (positions_disabled) return codes::ok; + + components::mesh_vertex::position position; + if (parse_numeric_vector(param, position) != std::errc{}) [[unlikely]] + { + return codes::obj_malformed_vertex; + } + + position_buffer.push_back(position); + + return codes::ok; + }), + ztu::make_line_parser("vt ", ztu::is_repeating, [&](const auto& param) { + if (tex_coords_disabled) return codes::ok; + + components::mesh_vertex::tex_coord coord; + if (parse_numeric_vector(param, coord) != std::errc{}) [[unlikely]] + { + return codes::obj_malformed_texture_coordinate; + } + + tex_coord_buffer.push_back(coord); + + return codes::ok; + }), + ztu::make_line_parser("vn ", ztu::is_repeating, [&](const auto& param) + { + if (normals_disabled) return codes::ok; + + components::mesh_vertex::normal normal; + if (parse_numeric_vector(param, normal) != std::errc{}) [[unlikely]] + { + return codes::obj_malformed_normal; + } + + normal_buffer.push_back(normal); + + return codes::ok; + }), + ztu::make_line_parser("o ", ztu::is_not_repeating, [&](const auto&) + { + push_mesh(); // Name is currently ignored + return codes::ok; + }), + ztu::make_line_parser("f ", ztu::is_repeating, [&](const auto& param) + { + const auto begin = param.begin().base(); + const auto end = param.end().base(); + + auto vertex = vertex_type{}; + + ztu::u32 first_index{}, prev_index{}; + + auto vertex_count = std::size_t{}; + + for (auto it = begin; it <= end; ++it) + { + + for (auto& component_index : vertex) + { + if (it != end and *it == '/') + { + ++it; + continue; + } + + const auto [ptr, ec] = std::from_chars(it, end, component_index); + if (ec != std::errc()) [[unlikely]] + { + // Discard whole face if one index is malformed + return codes::obj_malformed_face; + } + + --component_index; // Indices start at one + it = ptr; + + if (it == end or *it != '/') + { + break; + } + + ++it; + } + + ++vertex_count; + + if (it != end and *it != ' ') [[unlikely]] + { + return codes::obj_malformed_face; + } + + const auto curr_index = find_or_push_vertex(vertex); + + if (vertex_count >= 3) + { + auto& triangle = mesh.triangles().emplace_back(); + triangle[0] = first_index; + triangle[1] = prev_index; + triangle[2] = curr_index; + } + else if (vertex_count == 1) + { + first_index = curr_index; + } + + prev_index = curr_index; + } + + return codes::ok; + }), + ztu::make_line_parser("usemtl ", ztu::is_not_repeating, [&](const auto& param) + { + push_mesh(false); + + material_name = param; + + return codes::ok; + }), + ztu::make_line_parser("mtllib ", ztu::is_not_repeating, [&](const auto& param) + { + auto material_filename = fs::path(param); + if (material_filename.is_relative()) + { + material_filename = directory / material_filename; + } + + if (const auto error = ctx.material_loader.read( + ctx, + mtl_loader_id, + material_filename, + pedantic + )) { + ztu::logger::warn( + "Error occurred while loading mtl files '%': [%] %", + material_filename, + error.category().name(), + error.message() + ); + } + }) + ); + + material_loader.clear_name_lookup(); + + if (ec != codes::ok) + { + return make_error_code(ec); + } + + push_mesh(); + + return {}; +} diff --git a/source/assets/data_loaders/stl_loader.cpp b/source/assets/data_loaders/stl_loader.cpp new file mode 100644 index 0000000..ec35eaf --- /dev/null +++ b/source/assets/data_loaders/stl_loader.cpp @@ -0,0 +1,253 @@ +#include "assets/data_loaders/stl_loader.hpp" + +#include "util/binary_ifstream.hpp" +#include "util/unroll_bool_template.hpp" +#include "util/logger.hpp" + +template +std::error_code read_body( + binary_ifstream& in, + const std::uint32_t expected_triangle_count, + std::vector& positions, + std::vector& normals, + std::vector>& triangles +) { + + const auto read_vector = [&in](auto& vector) -> std::error_code + { + for (auto& component : vector) + { + float component32; + if (const auto e = in.read_ieee754(component32)) + { + return e; + } + component = component32; + } + return {}; + }; + + for (std::uint32_t i{}; i != expected_triangle_count; ++i) { + + auto normal = components::mesh_vertex::normal{}; + if constexpr (Normals) + { + if (const auto e = read_vector(normal)) + { + return e; + } + } + + auto triangle = std::array{}; + + for (auto& index : triangle) { + + auto position = components::mesh_vertex::position{}; + if (const auto e = read_vector(position)) + { + return e; + } + + // TODO implement unique insert correctly + /*// Insert vertices sorted, to efficiently remove duplicates. + const auto it = std::ranges::upper_bound(positions, position); + + // Set index before `it` is invalidated by insert. + index = it - positions.begin(); + + if (it != positions.begin() and *std::prev(it) == position) + { + --index; + } + else + { + positions.insert(it, position); + if constexpr (Normals) + { + normals.insert(normals.begin() + index, normal); + } + }*/ + + index = positions.size(); + positions.push_back(position); + + if constexpr (Normals) + { + normals.push_back(normal); + } + } + + triangles.push_back(triangle); + + // Skip attribute bytes + if (const auto e = in.skip()) + { + return e; + } + } + + return {}; +} + +std::error_code stl_loader::read_directory( + const std::filesystem::path& path, + std::vector& meshes, + components::mesh_vertex::flags enabled_components::mesh_vertexs, + std::vector& materials, + material_component::flags enabled_material_components, + const ztu::u32 base_material_id, + bool pedantic +) { + namespace fs = std::filesystem; + + if (not fs::exists(path)) { + return make_error_code(std::errc::no_such_file_or_directory); + } + + for (const auto& file : fs::directory_iterator{ path / "frames" }) + { + const auto& file_path = file.path(); + + if (file_path.extension() != ".stl") + { + continue; + } + + if (const auto e = read( + file_path, + meshes, + enabled_components::mesh_vertexs, + materials, + enabled_material_components, + base_material_id, + pedantic + )) { + ztu::logger::error( + "Error while loading stl file '%': [%] %", + file_path, + e.category().name(), + e.message() + ); + } + } + + return {}; +} + + +std::error_code stl_loader::read( + const std::filesystem::path& filename, + std::vector& meshes, + components::mesh_vertex::flags enabled_components::mesh_vertexs, + std::vector&, + material_component::flags, + ztu::u32, + const bool pedantic +) { + auto error = std::error_code{}; + + auto in = binary_ifstream{}; + + if ((error = in.open(filename, true))) + { + return error; + } + + auto header_bytes_left = static_cast(80); + + if (pedantic) + { + // Check if ASCII file was provided, these start with a specific character sequence. + + static constexpr auto ascii_magic_string = std::string_view("solid"); + + auto magic_bytes = std::array{}; + + if ((error = in.read(magic_bytes))) + { + return error; + } + + const auto magic_string = std::string_view( + reinterpret_cast(magic_bytes.data()), + magic_bytes.size() + ); + + if (magic_string == ascii_magic_string) + { + return std::make_error_code(std::errc::illegal_byte_sequence); + } + + header_bytes_left -= ascii_magic_string.size(); + } + + // Ignore (rest of) header. + if ((error = in.skip(header_bytes_left))) + { + return error; + } + + // Read number of bytes + auto expected_triangle_count = std::uint32_t{}; + + if ((error = in.read(expected_triangle_count))) + { + return error; + } + + // Use separate mesh for parsing, so original mesh is only overwritten + // if no errors occurred. This also guarantees unused reserved memory + // is freed immediately in case of an error. + auto mesh = dynamic_mesh_data{}; + + auto& positions = mesh.positions(); + auto& normals = mesh.normals(); + auto& triangles = mesh.triangles(); + auto& material_id = mesh.material_id(); + + material_id = 0; // Set to default material + + positions.reserve(expected_triangle_count * 3); + normals.reserve(expected_triangle_count); + triangles.reserve(expected_triangle_count); + + const auto normals_enabled = ( + (enabled_components::mesh_vertexs & components::mesh_vertex::flags::normal) != components::mesh_vertex::flags::none + ); + + error = unroll_bool_function_template([&]() { + return read_body( + in,expected_triangle_count, + positions, + normals, + triangles + ); + }, normals_enabled); + + // Free any unused reserved memory + positions.shrink_to_fit(); + normals.shrink_to_fit(); + triangles.shrink_to_fit(); + + if (error) + { + return error; + } + + ztu::logger::debug("Normal count: %", normals.size()); + + if (not positions.empty()) + { + mesh.components() |= components::mesh_vertex::flags::position; + } + + if (not normals.empty()) + { + ztu::logger::debug("Enabling normals!!!"); + mesh.components() |= components::mesh_vertex::flags::normal; + } + + meshes.emplace_back(std::move(mesh)); + + return {}; +} \ No newline at end of file diff --git a/source/assets/data_loaders/threedtk_pose_loader.cpp b/source/assets/data_loaders/threedtk_pose_loader.cpp new file mode 100644 index 0000000..724acb0 --- /dev/null +++ b/source/assets/data_loaders/threedtk_pose_loader.cpp @@ -0,0 +1,136 @@ +#include "assets/data_loaders/threedtk_pose_loader.hpp" + +#include "assets/dynamic_read_buffers/dynamic_pose_buffer.hpp" + +#include +#include +#include + +#include "util/logger.hpp" + + + +inline std::error_code threedtk_pose_loader::parse_transform_info( + std::ifstream& in, + std::string& line, + std::array& transform_info +) { + + for (std::size_t row{}; row != 2; ++row) { + + std::getline(in, line); + + auto it = line.cbegin().base(); + auto end = line.cend().base(); + + for (glm::vec3::length_type col{}; col != 3; ++col) { + + const auto [ ptr, ec ] = std::from_chars( + it, end, + transform_info[row][col], + std::chars_format::general + ); + + if (ec != std::errc{}) { + return std::make_error_code(ec); + } + + it = ptr + 1; // skip space in between components + } + } +} + +inline ztu::result threedtk_pose_loader::parse_index( + const std::string_view filename +) { + static constexpr auto prefix = std::string_view{ "scan" }; + + auto name_view = filename.substr(0, name_view.find('.')); + + if (name_view.length() <= prefix.length()) [[unlikely]] + { + return std::make_error_code(std::errc::invalid_argument); + } + + name_view = name_view.substr(prefix.length()); + + pose_prefetch_lookup::index_type index; + + const auto res = std::from_chars(name_view.begin(), name_view.end(), index); + + if (res.ec != std::errc{}) [[unlikely]] + { + return std::make_error_code(res.ec); + } + + return index; +} + + +void threedtk_pose_loader::load( + const ztu::string_list& filenames, + dynamic_pose_store& store, + pose_prefetch_lookup& id_lookup +) { + + auto filename_buffer = std::string{}; + auto in = std::ifstream{}; + auto line = std::string{}; + auto pose_buffer = dynamic_pose_buffer{}; + + for (const auto filename : filenames) + { + + pose_prefetch_lookup::index_type index; + if (const auto res = parse_index(filename)) + { + index = *res; + } + else + { + const auto error = res.error(); + ztu::logger::error( + "Error while parsing 3dtk pose file index %: [%] %", + filename, + error.category().name(), + error.message() + ); + } + + filename_buffer = filename; + in.open(filename_buffer.c_str()); + if (not in.is_open()) { + ztu::logger::error("Cannot open 3dtk pose file %", filename); + continue; + } + + std::array transform_info{}; + + const auto error = parse_transform_info(in, line, transform_info); + + in.close(); + + if (error) + { + ztu::logger::error( + "Error while parsing 3dtk pose file %: [%] %", + filename, + error.category().name(), + error.message() + ); + continue; + } + + const auto& translation = transform_info[0]; + auto& angles = transform_info[1]; + angles *= static_cast(M_PI / 180.0); + + pose_buffer = ( + glm::translate(glm::identity(), translation) * + glm::eulerAngleXYZ(angles[0], angles[1], angles[2]) + ); + + const auto id = store.add(pose_buffer); + id_lookup.emplace(filename, index, id); + } +} diff --git a/source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp b/source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp new file mode 100644 index 0000000..32641aa --- /dev/null +++ b/source/assets/dynamic_data_loaders/dynamic_mesh_loader.cpp @@ -0,0 +1,41 @@ +#include "assets/dynamic_data_loaders/dynamic_mesh_loader.hpp" + +std::error_code dynamic_mesh_loader::prefetch( + loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue +) { + return this->invoke_with_matching_loader( + loader_id, + [&](auto& loader) + { + return loader.prefetch( + directories, + queue + ); + } + ); +} + +std::error_code dynamic_mesh_loader::load( + loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_mesh_store& store, + mesh_prefetch_lookup& id_lookup, + const bool pedantic +) { + return this->invoke_with_matching_loader( + loader_id, + [&](auto& loader) + { + return loader.load( + m_buffer, + directories, + store, + id_lookup, + pedantic + ); + } + ); +} + diff --git a/source/assets/dynamic_data_loaders/dynamic_point_cloud_loader.cpp b/source/assets/dynamic_data_loaders/dynamic_point_cloud_loader.cpp new file mode 100644 index 0000000..ceadafb --- /dev/null +++ b/source/assets/dynamic_data_loaders/dynamic_point_cloud_loader.cpp @@ -0,0 +1,40 @@ +#include "assets/dynamic_data_loaders/dynamic_point_cloud_loader.hpp" + +std::error_code dynamic_point_cloud_loader::prefetch( + const loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue +) { + return this->invoke_with_matching_loader( + loader_id, + [&](auto& loader) + { + return loader.prefetch( + directories, + queue + ); + } + ); +} + +std::error_code dynamic_point_cloud_loader::load( + const loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_point_cloud_store& store, + point_cloud_prefetch_lookup& id_lookup, + const bool pedantic +) { + return this->invoke_with_matching_loader( + loader_id, + [&](auto& loader) + { + return loader.load( + m_buffer, + directories, + store, + id_lookup, + pedantic + ); + } + ); +} \ No newline at end of file diff --git a/source/assets/dynamic_data_loaders/dynamic_texture_loader.cpp b/source/assets/dynamic_data_loaders/dynamic_texture_loader.cpp new file mode 100644 index 0000000..a001239 --- /dev/null +++ b/source/assets/dynamic_data_loaders/dynamic_texture_loader.cpp @@ -0,0 +1,122 @@ +#include "assets/dynamic_data_loaders/dynamic_texture_loader.hpp" + +#if defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC system_header +#elif defined(_MSC_VER) +#pragma warning(push, 0) +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" + +#if defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include "util/logger.hpp" + +dynamic_texture_loader::dynamic_texture_loader(components::texture::flags enabled_components) : + m_enabled_components{ enabled_components }, + m_loader_id_lookup{ + { "jpg", loader_id_type{ 0 } }, + { "png", loader_id_type{ 1 } }, + { "tga", loader_id_type{ 2 } }, + { "bmp", loader_id_type{ 3 } }, + { "psd", loader_id_type{ 4 } }, + { "gif", loader_id_type{ 5 } }, + { "hdr", loader_id_type{ 6 } }, + { "pic", loader_id_type{ 7 } } + } {} + +std::optional dynamic_texture_loader::find_loader(const std::string_view& name) +{ + const auto it = m_loader_id_lookup.find(name); + + if (it != m_loader_id_lookup.end()) + { + return it->second; + } + + return std::nullopt; +} + +std::error_code dynamic_texture_loader::prefetch( + const loader_id_type loader_id, + const ztu::string_list& directories, + prefetch_queue& queue +) { + // Nothing to prefetch... + return {}; +} + +std::error_code dynamic_texture_loader::load( + const loader_id_type loader_id, + const ztu::string_list& directories, + dynamic_texture_store& store, + texture_prefetch_lookup& id_lookup, + const bool pedantic +) { + stbi_set_flip_vertically_on_load(true); + + int width, height, channels; + + for (const auto filename : directories) + { + const auto id_it = id_lookup.find(filename); + + if (id_it != id_lookup.end()) [[unlikely]] + { + continue; + } + + auto ptr = reinterpret_cast(stbi_load( + filename.data(), // Null terminated by string_list + &width, + &height, + &channels, + 0 + )); + + if (ptr == nullptr) { + return std::make_error_code(std::errc::no_such_file_or_directory); + } + + auto data = std::unique_ptr(ptr); + + using flags = components::texture::flags; + + auto components = flags{}; + switch (channels) + { + case 1: + components = flags::luminance; + break; + case 2: + components = flags::luminance | flags::alpha; + break; + case 3: + components = flags::red | flags::green | flags::blue; + break; + case 4: + components = flags::red | flags::green | flags::blue | flags::alpha; + break; + default: [[unlikely]] + ztu::logger::error("Unsupported pixel component composition %", static_cast(components)); + continue; + } + + const auto id = store.add(dynamic_texture_buffer( + std::move(data), + width, + height, + components + )); + id_lookup.emplace_hint(id_it, filename, id); + } + + return {}; +} diff --git a/source/assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp b/source/assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp new file mode 100644 index 0000000..c3835e8 --- /dev/null +++ b/source/assets/dynamic_data_loaders/generic/base_dynamic_loader.ipp @@ -0,0 +1,92 @@ +#ifndef INCLUDE_BASE_DYNAMIC_LOADER_IMPLEMENTATION +# error Never include this file directly include 'base_dynamic_loader.hpp' +#endif + +#include "util/for_each.hpp" + +template +base_dynamic_loader::base_dynamic_loader(const C enabled_components) : + m_enabled_components{ enabled_components } +{ + [&](std::index_sequence) { + m_loader_id_lookup = { { std::string{ Loaders::name }, { Is } }... }; + }(std::index_sequence_for()); +} + +template +std::optional::loader_id_type> base_dynamic_loader::find_loader( + std::string_view name +) { + const auto it = m_loader_id_lookup.find(name); + + if (it != m_loader_id_lookup.end()) + { + return it->second; + } + + return std::nullopt; +} + +template +consteval std::optional::loader_id_type> base_dynamic_loader::find_loader_static( + std::string_view name +) { + constexpr auto invalid_index = std::numeric_limits::max(); + + auto index = invalid_index; + ztu::for_each::indexed_type([&]() + { + if (name == Loader::name) + { + index = Index; + return true; + } + return false; + }); + + return index == invalid_index ? std::nullopt : loader_id_type{ index }; +} + +template +template::loader_id_type ID> +auto& base_dynamic_loader::get_loader() +{ + return std::get(m_loaders); +} + +template +template +ztu::result base_dynamic_loader::invoke_with_matching_loader( + const loader_id_type loader_id, F&& f +) { + return std::apply( + [&](Loaders&... loaders) + { + return [&](std::index_sequence) + { + std::error_code error; + + const auto found_parser = ( + [&](auto& loader, const std::size_t index) + { + if (loader_id == index) + { + error = f(loader); + return true; + } + return false; + } (loaders, Is) + or ... + ); + + if (not found_parser) + { + error = std::make_error_code(std::errc::invalid_argument); + } + + return error; + }(std::index_sequence_for()); + }, + m_loaders + ); +} diff --git a/source/assets/dynamic_data_stores/dynamic_material_store.cpp b/source/assets/dynamic_data_stores/dynamic_material_store.cpp new file mode 100644 index 0000000..70ce9ee --- /dev/null +++ b/source/assets/dynamic_data_stores/dynamic_material_store.cpp @@ -0,0 +1,26 @@ +#include "assets/dynamic_data_stores/dynamic_material_store.hpp" + +dynamic_material_store::id_type dynamic_material_store::add(const dynamic_material_buffer& buffer) +{ + return m_store.add(buffer.data); +} + +std::pair dynamic_material_store::find(const id_type id) +{ + return m_store.find(id); +} + +std::pair dynamic_material_store::find(const id_type id) const +{ + return m_store.find(id); +} + +void dynamic_material_store::remove(const iterator_type& it) +{ + m_store.remove(it); +} + +void dynamic_material_store::clear() +{ + m_store.clear(); +} diff --git a/source/assets/dynamic_data_stores/dynamic_mesh_store.cpp b/source/assets/dynamic_data_stores/dynamic_mesh_store.cpp new file mode 100644 index 0000000..6eb6c3d --- /dev/null +++ b/source/assets/dynamic_data_stores/dynamic_mesh_store.cpp @@ -0,0 +1,31 @@ +#include "assets/dynamic_data_stores/dynamic_mesh_store.hpp" + + +dynamic_mesh_store::id_type dynamic_mesh_store::add(const dynamic_mesh_buffer& mesh_buffer) +{ + const auto& triangles = mesh_buffer.triangles(); + return m_store.add( + std::span{ triangles.front().data(), triangles.size() * 3 }, + mesh_buffer.vertices + ); +} + +std::pair dynamic_mesh_store::find(id_type id) +{ + return m_store.find(id); +} + +std::pair dynamic_mesh_store::find(id_type id) const +{ + return m_store.find(id); +} + +void dynamic_mesh_store::remove(const iterator_type& it) +{ + m_store.remove(it); +} + +void dynamic_mesh_store::clear() +{ + m_store.clear(); +} diff --git a/source/assets/dynamic_data_stores/dynamic_point_cloud_store.cpp b/source/assets/dynamic_data_stores/dynamic_point_cloud_store.cpp new file mode 100644 index 0000000..25860b2 --- /dev/null +++ b/source/assets/dynamic_data_stores/dynamic_point_cloud_store.cpp @@ -0,0 +1,27 @@ +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" + + +dynamic_point_cloud_store::id_type dynamic_point_cloud_store::add(const dynamic_point_cloud_buffer& mesh_buffer) +{ + return m_store.add(mesh_buffer.vertices); +} + +std::pair dynamic_point_cloud_store::find(id_type id) +{ + return m_store.find(id); +} + +std::pair dynamic_point_cloud_store::find(id_type id) const +{ + return m_store.find(id); +} + +void dynamic_point_cloud_store::remove(const iterator_type& it) +{ + m_store.remove(it); +} + +void dynamic_point_cloud_store::clear() +{ + m_store.clear(); +} diff --git a/source/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp b/source/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp new file mode 100644 index 0000000..bc83c1c --- /dev/null +++ b/source/assets/dynamic_data_stores/generic/generic_dynamic_component_array_store.ipp @@ -0,0 +1,387 @@ +#ifndef INCLUDE_GENERIC_DYNAMIC_COMPONENT_ARRAY_STORE_IMPLEMENTATION +# error Never include this file directly include 'generic_dynamic_component_array_store.hpp' +#endif +#include + +template +component_array_iterator::component_array_iterator( + component_array_pointer_type components, + flag_count_pointer_type flags, + std::size_t index, + const offsets_type& offsets +) : + m_components{ components }, + m_flag_counts{ flags }, + m_index{ index }, + m_offsets{ offsets } {} + + +template +typename component_array_iterator::reference component_array_iterator::operator*() const { + return dereference(std::index_sequence_for{}); +} + +template +component_array_iterator& component_array_iterator::operator++() { + adjust_offsets(std::index_sequence_for{}, 1); + ++m_index; + return *this; +} + +template +component_array_iterator component_array_iterator::operator++(int) { + component_array_iterator tmp = *this; + ++(*this); + return tmp; +} + +template +component_array_iterator& component_array_iterator::operator--() { + adjust_offsets(std::index_sequence_for{}, -1); + --m_index; + return *this; +} + +template +component_array_iterator component_array_iterator::operator--(int) { + auto tmp = *this; + --(*this); + return tmp; +} + +template +component_array_iterator& component_array_iterator::operator+=(const difference_type n) +{ + adjust_offsets(std::index_sequence_for{}, n); + m_index += n; + return *this; +} + +template +component_array_iterator& component_array_iterator::operator-=(const difference_type n) +{ + return (*this) += -n; +} + +template +component_array_iterator component_array_iterator::operator+(const difference_type n) const +{ + auto tmp = *this; + return tmp += n; +} + +template +component_array_iterator component_array_iterator::operator-(const difference_type n) const +{ + auto tmp = *this; + return tmp -= n; +} + +template +typename component_array_iterator::difference_type +component_array_iterator::operator-(const component_array_iterator& other) const +{ + return static_cast(m_index) - static_cast(other.m_index); +} + +template +typename component_array_iterator::reference component_array_iterator::operator[]( + const difference_type n +) const { + return *((*this) + n); +} + +template +bool component_array_iterator::operator==(const component_array_iterator& other) const +{ + return m_components == other.m_components and m_index == other.m_index; +} + +template +bool component_array_iterator::operator!=(const component_array_iterator& other) const +{ + return not (*this == other); +} + +template +bool component_array_iterator::operator<(const component_array_iterator& other) const +{ + return m_index < other.m_index; +} + +template +bool component_array_iterator::operator<=(const component_array_iterator& other) const +{ + return m_index <= other.m_index; +} + +template +bool component_array_iterator::operator>(const component_array_iterator& other) const +{ + return m_index > other.m_index; +} + +template +bool component_array_iterator::operator>=(const component_array_iterator& other) const +{ + return m_index >= other.m_index; +} + +template +template +bool component_array_iterator::is_component_enabled(C flag) +{ + return (flag & (C{1} << I)) != C{}; +} + +template +template +void component_array_iterator::calc_offsets(std::index_sequence, difference_type n) +{ + + const auto negative = n < difference_type{ 0 }; + const auto positive = n > difference_type{ 0 }; + const auto step = difference_type{ positive } - difference_type{ negative }; + n = negative ? -n : n; + + // TODO template optimize for single steps + + while (n--) + { + const auto& [ flags, count ] = m_flag_counts[m_index + n]; + + ([&] { + if (is_component_enabled(flags)) { + std::get(m_offsets) += step * count; + } + }(), ...); + + m_index += step; + } +} + +template +template +typename component_array_iterator::reference +component_array_iterator::dereference(std::index_sequence) const +{ + return std::make_tuple(get_span()...); +} + +template +template +std::tuple_element_t::value_type> +component_array_iterator::get_span() const +{ + const auto& [ flags, count ] = m_flag_counts[m_index]; + + if (is_component_enabled(flags)) + { + return { &std::get(m_components)[m_offsets[N]], count }; + } + return nullptr; +} + + +template +std::tuple...> generic_dynamic_component_array_store::data_ptrs() +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_component_arrays).data()...); + } + (std::index_sequence_for{}); +} + +template +std::tuple>...> generic_dynamic_component_array_store::data_ptrs() const +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_component_arrays).data()...); + } + (std::index_sequence_for{}); +} + +template +std::array generic_dynamic_component_array_store::data_counts() const +{ + return [&](std::index_sequence) + { + return std::array{ std::get(m_component_arrays).size()... }; + } + (std::index_sequence_for{}); +} + +template +typename generic_dynamic_component_array_store::id_type generic_dynamic_component_array_store::add( + const std::tuple...>& component_arrays +) { + + auto component_flags = C{}; + auto count = count_type{}; + + [&](std::integer_sequence) + { + const auto& array = std::get(component_arrays); + if (not array.empty()) + { + const auto array_count = static_cast(array.size()); + count = count ? std::min(array_count, count) : array_count; + component_flags |= C{ 1 } << Is; + } + } + (std::index_sequence_for{}); + + + [&](std::integer_sequence) + { + const auto& src_array = std::get(component_arrays); + auto& dst_array = std::get(m_component_arrays); + + if (not src_array.empty()) + { + dst_array.insert(dst_array.end(), src_array.begin(), src_array.begin() + count); + } + } + (std::index_sequence_for{}); + + m_component_flag_counts.emplace_back(component_flags, count); + + const auto id = id_type{ m_next_data_id.index++ }; + m_ids.push_back(id); + + return id; +} + +template +std::pair::iterator_type, bool> generic_dynamic_component_array_store::find(id_type id) +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + +template +std::pair::const_iterator, bool> generic_dynamic_component_array_store::find(id_type id) const +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + + +template +void generic_dynamic_component_array_store::remove(const iterator_type& it) +{ + [&](std::index_sequence) + { + ([&]{ + auto& component_vector = std::get(m_component_arrays); + const auto begin = component_vector.begin() + it.m_offsets[Is]; + const auto end = begin + it.m_flag_counts[it.m_index]; + component_vector.erase(begin, end); + }(), ...); + } (std::index_sequence_for{}); + + m_component_flag_counts.erase(m_component_flag_counts.begin() + it.m_index); + m_ids.erase(m_ids.begin() + it.m_index); +} + +template +void generic_dynamic_component_array_store::clear() +{ + [&](std::index_sequence) + { + std::get(m_component_arrays).clear(); + } (std::index_sequence_for{}); + m_component_flag_counts.clear(); + m_ids.clear(); +} + +template +typename generic_dynamic_component_array_store::iterator_type generic_dynamic_component_array_store::begin() +{ + return iterator_type{ + data_ptrs(), + m_component_flag_counts.data(), + 0, + {} + }; +} + +template +typename generic_dynamic_component_array_store::iterator_type generic_dynamic_component_array_store::end() +{ + return iterator_type{ + data_ptrs(), + m_component_flag_counts.data(), + m_component_flag_counts.size(), + data_counts() + }; +} + +template +typename generic_dynamic_component_array_store::const_iterator generic_dynamic_component_array_store::begin() const +{ + return iterator_type{ + data_ptrs(), + m_component_flag_counts.data(), + 0, + {} + }; +} + +template +typename generic_dynamic_component_array_store::const_iterator generic_dynamic_component_array_store::end() const +{ + return iterator_type{ + data_ptrs(), + m_component_flag_counts.data(), + m_component_flag_counts.size(), + data_counts() + }; +} + +template +typename generic_dynamic_component_array_store::const_iterator generic_dynamic_component_array_store::cbegin() const +{ + return const_cast(this)->begin(); +} + +template +typename generic_dynamic_component_array_store::const_iterator generic_dynamic_component_array_store::cend() const +{ + return const_cast(this)->end(); +} + +template +typename generic_dynamic_component_array_store::view_type generic_dynamic_component_array_store::view() +{ + return { begin(), end() }; +} +template +typename generic_dynamic_component_array_store::const_view_type generic_dynamic_component_array_store::view() const +{ + return { begin(), end() }; +} diff --git a/source/assets/dynamic_data_stores/generic/generic_dynamic_component_store.ipp b/source/assets/dynamic_data_stores/generic/generic_dynamic_component_store.ipp new file mode 100644 index 0000000..05447cd --- /dev/null +++ b/source/assets/dynamic_data_stores/generic/generic_dynamic_component_store.ipp @@ -0,0 +1,373 @@ +#ifndef INCLUDE_GENERIC_DYNAMIC_COMPONENT_STORE_IMPLEMENTATION +# error Never include this file directly include 'basic_dynamic_component_store.hpp' +#endif + +#pragma once + +#include +#include + +#include "util/uix.hpp" +#include "util/id_type.hpp" + +#include +#include +#include +#include + +template +component_iterator::component_iterator( + const value_type components, + const C* flags, + std::size_t index, + const offsets_type& offsets +) : m_components{ components }, m_flags{ flags }, m_index{ index }, m_offsets{ offsets } {} + + +template +typename component_iterator::reference component_iterator::operator*() const { + return dereference(std::index_sequence_for{}); +} + +template +component_iterator& component_iterator::operator++() { + adjust_offsets(std::index_sequence_for{}, 1); + ++m_index; + return *this; +} + +template +component_iterator component_iterator::operator++(int) { + component_iterator tmp = *this; + ++(*this); + return tmp; +} + +template +component_iterator& component_iterator::operator--() { + adjust_offsets(std::index_sequence_for{}, -1); + --m_index; + return *this; +} + +template +component_iterator component_iterator::operator--(int) { + auto tmp = *this; + --(*this); + return tmp; +} + +template +component_iterator& component_iterator::operator+=(const difference_type n) +{ + adjust_offsets(std::index_sequence_for{}, n); + m_index += n; + return *this; +} + +template +component_iterator& component_iterator::operator-=(const difference_type n) +{ + return (*this) += -n; +} + +template +component_iterator component_iterator::operator+(const difference_type n) const +{ + auto tmp = *this; + return tmp += n; +} + +template +component_iterator component_iterator::operator-(const difference_type n) const +{ + auto tmp = *this; + return tmp -= n; +} + +template +typename component_iterator::difference_type +component_iterator::operator-(const component_iterator& other) const +{ + return static_cast(m_index) - static_cast(other.m_index); +} + +template +typename component_iterator::reference component_iterator::operator[]( + const difference_type n +) const { + return *((*this) + n); +} + +template +bool component_iterator::operator==(const component_iterator& other) const +{ + return m_components == other.m_components and m_index == other.m_index; +} + +template +bool component_iterator::operator!=(const component_iterator& other) const +{ + return not (*this == other); +} + +template +bool component_iterator::operator<(const component_iterator& other) const +{ + return m_index < other.m_index; +} + +template +bool component_iterator::operator<=(const component_iterator& other) const +{ + return m_index <= other.m_index; +} + +template +bool component_iterator::operator>(const component_iterator& other) const +{ + return m_index > other.m_index; +} + +template +bool component_iterator::operator>=(const component_iterator& other) const +{ + return m_index >= other.m_index; +} + +template +template +bool component_iterator::is_component_enabled(C flag) +{ + return (flag & (C{1} << I)) != C{}; +} + +template +template +void component_iterator::calc_offsets(std::index_sequence, difference_type n) +{ + + const auto negative = n < difference_type{ 0 }; + const auto positive = n > difference_type{ 0 }; + const auto step = difference_type{ positive } - difference_type{ negative }; + n = negative ? -n : n; + + // TODO template optimize for single steps + + while (n--) + { + const C& flag = m_flags[m_index + n]; + + ([&] { + if (is_component_enabled(flag)) { + std::get(m_offsets) += step; + } + }(), ...); + + m_index += step; + } +} + +template +template +typename component_iterator::reference +component_iterator::dereference(std::index_sequence) const +{ + return std::make_tuple(get_pointer()...); +} + +template +template +std::tuple_element_t::value_type> +component_iterator::get_pointer() const +{ + if (is_component_enabled(m_flags[m_index])) + { + return &std::get(m_components)[m_offsets[N]]; + } + return nullptr; +} + + +template +std::tuple...> component_iterator::data_ptrs() +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_components).data()...); + } + (std::index_sequence_for{}); +} + +template +std::tuple>...> component_iterator::data_ptrs() const +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_components).data()...); + } + (std::index_sequence_for{}); +} + +template +std::array component_iterator::data_counts() const +{ + return [&](std::index_sequence) + { + return std::array{ std::get(m_components).size()... }; + } + (std::index_sequence_for{}); +} + +template +typename generic_dynamic_component_store::id_type generic_dynamic_component_store::add( + const std::tuple...>& data +) { + + auto component_flags = C{}; + + [&](std::integer_sequence) + { + if (const auto& component_opt = std::get(data)) + { + std::get(m_components).push_back(*component_opt); + component_flags |= C{ 1 } << Is; + } + } + (std::index_sequence_for{}); + + m_component_flags.push_back(component_flags); + + const auto id = id_type{ m_next_data_id.index++ }; + m_ids.push_back(id); + + return id; +} + +template +std::pair::iterator_type, bool> generic_dynamic_component_store::find(id_type id) +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + +template +std::pair::const_iterator, bool> generic_dynamic_component_store::find(id_type id) const +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + + +template +void generic_dynamic_component_store::remove(const iterator_type& it) +{ + return [&](std::index_sequence) + { + auto& component_vector = std::get(m_components); + (component_vector.erase(component_vector.begin() + it.m_offsets[Is]), ...); + } (std::index_sequence_for{}); + + m_component_flags.erase(m_component_flags.begin() + it.m_index); + m_ids.erase(m_ids.begin() + it.m_index); +} + +template +void generic_dynamic_component_store::clear() +{ + return [&](std::index_sequence) + { + std::get(m_component_counts).clear(); + } (std::index_sequence_for{}); + m_component_flags.clear(); + m_ids.clear(); +} + +template +generic_dynamic_component_store::iterator_type generic_dynamic_component_store::begin() +{ + return iterator_type{ + data_ptrs(), + m_component_flags.data(), + 0, + {} + }; +} + +template +generic_dynamic_component_store::iterator_type generic_dynamic_component_store::end() +{ + return iterator_type{ + data_ptrs(), + m_component_flags.data(), + m_component_flags.size(), + data_counts() + }; +} + +template +generic_dynamic_component_store::const_iterator generic_dynamic_component_store::begin() const +{ + return iterator_type{ + data_ptrs(), + m_component_flags.data(), + 0, + {} + }; +} + +template +generic_dynamic_component_store::const_iterator generic_dynamic_component_store::end() const +{ + return iterator_type{ + data_ptrs(), + m_component_flags.data(), + m_component_flags.size(), + data_counts() + }; +} + +template +generic_dynamic_component_store::const_iterator generic_dynamic_component_store::cbegin() const +{ + return const_cast(this)->begin(); +} + +template +generic_dynamic_component_store::const_iterator generic_dynamic_component_store::cend() const +{ + return const_cast(this)->end(); +} + +template +generic_dynamic_component_store::view_type generic_dynamic_component_store::view() +{ + return { begin(), end() }; +} +template +generic_dynamic_component_store::const_view_type generic_dynamic_component_store::view() const +{ + return { begin(), end() }; +} diff --git a/source/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.ipp b/source/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.ipp new file mode 100644 index 0000000..dd4f067 --- /dev/null +++ b/source/assets/dynamic_data_stores/generic/generic_dynamic_indexed_component_array_store.ipp @@ -0,0 +1,418 @@ +#ifndef INCLUDE_GENERIC_DYNAMIC_INDEXED_COMPONENT_ARRAY_STORE_IMPLEMENTATION +# error Never include this file directly include 'generic_dynamic_indexed_component_array_store.hpp' +#endif + +#pragma once + +#include +#include +#include +#include +#include +#include + +template +indexed_component_array_iterator::indexed_component_array_iterator( + const index_array_pointer_type indices, + const component_array_pointer_type& components, + const flag_count_pointer_type flag_counts, + std::size_t index, + const offsets_type& offsets +) : + m_indices{ indices }, + m_components{ components }, + m_flag_counts{ flag_counts }, + m_index{ index }, + m_offsets{ offsets } {} + + +template +typename indexed_component_array_iterator::reference indexed_component_array_iterator::operator*() const { + return dereference(std::index_sequence_for{}); +} + +template +indexed_component_array_iterator& indexed_component_array_iterator::operator++() { + adjust_offsets(std::index_sequence_for{}, 1); + ++m_index; + return *this; +} + +template +indexed_component_array_iterator indexed_component_array_iterator::operator++(int) { + indexed_component_array_iterator tmp = *this; + ++(*this); + return tmp; +} + +template +indexed_component_array_iterator& indexed_component_array_iterator::operator--() { + adjust_offsets(std::index_sequence_for{}, -1); + --m_index; + return *this; +} + +template +indexed_component_array_iterator indexed_component_array_iterator::operator--(int) { + auto tmp = *this; + --(*this); + return tmp; +} + +template +indexed_component_array_iterator& indexed_component_array_iterator::operator+=(const difference_type n) +{ + adjust_offsets(std::index_sequence_for{}, n); + m_index += n; + return *this; +} + +template +indexed_component_array_iterator& indexed_component_array_iterator::operator-=(const difference_type n) +{ + return (*this) += -n; +} + +template +indexed_component_array_iterator indexed_component_array_iterator::operator+(const difference_type n) const +{ + auto tmp = *this; + return tmp += n; +} + +template +indexed_component_array_iterator indexed_component_array_iterator::operator-(const difference_type n) const +{ + auto tmp = *this; + return tmp -= n; +} + +template +typename indexed_component_array_iterator::difference_type +indexed_component_array_iterator::operator-(const indexed_component_array_iterator& other) const +{ + return static_cast(m_index) - static_cast(other.m_index); +} + +template +typename indexed_component_array_iterator::reference indexed_component_array_iterator::operator[]( + const difference_type n +) const { + return *((*this) + n); +} + +template +bool indexed_component_array_iterator::operator==(const indexed_component_array_iterator& other) const +{ + return m_components == other.m_components and m_index == other.m_index; +} + +template +bool indexed_component_array_iterator::operator!=(const indexed_component_array_iterator& other) const +{ + return not (*this == other); +} + +template +bool indexed_component_array_iterator::operator<(const indexed_component_array_iterator& other) const +{ + return m_index < other.m_index; +} + +template +bool indexed_component_array_iterator::operator<=(const indexed_component_array_iterator& other) const +{ + return m_index <= other.m_index; +} + +template +bool indexed_component_array_iterator::operator>(const indexed_component_array_iterator& other) const +{ + return m_index > other.m_index; +} + +template +bool indexed_component_array_iterator::operator>=(const indexed_component_array_iterator& other) const +{ + return m_index >= other.m_index; +} + +template +template +bool indexed_component_array_iterator::is_component_enabled(C flag) +{ + return (flag & (C{1} << N)) != C{}; +} + +template +template +void indexed_component_array_iterator::calc_offsets( + std::index_sequence, + difference_type n +) { + + const auto negative = n < difference_type{ 0 }; + const auto positive = n > difference_type{ 0 }; + const auto step = difference_type{ positive } - difference_type{ negative }; + n = negative ? -n : n; + + // TODO template optimize for single steps + + while (n--) + { + const auto& [ flags, index_count, component_count ] = m_flag_counts[m_index + n]; + + std::get<0>(m_offsets) += step * index_count; + + ([&] { + if (is_component_enabled(flags)) { + std::get<1 + Is>(m_offsets) += step * component_count; + } + }(), ...); + + m_index += step; + } +} + +template +template +typename indexed_component_array_iterator::reference +indexed_component_array_iterator::dereference(std::index_sequence) const +{ + const auto& [ flags, index_count, component_count ] = m_flag_counts[m_index]; + + return std::make_tuple( + std::span( + m_indices[m_offsets[0]], + index_count + ), + std::span( + ( + is_component_enabled(flags) + ? &std::get(m_components)[m_offsets[1 + Is]] + : nullptr + ), + component_count + )... + ); +} + + + +template +std::tuple...> generic_dynamic_indexed_component_array_store::component_array_ptrs() +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_component_arrays).data()...); + } + (std::index_sequence_for{}); +} + +template +std::tuple>...> generic_dynamic_indexed_component_array_store::component_array_ptrs() const +{ + return [&](std::index_sequence) + { + return std::make_tuple(std::get(m_component_arrays).data()...); + } + (std::index_sequence_for{}); +} + +template +std::array generic_dynamic_indexed_component_array_store::array_counts() const +{ + return [&](std::index_sequence) + { + return std::array{ + m_indices.size(), + std::get(m_component_arrays).size()... + }; + } + (std::index_sequence_for{}); +} + +template +typename generic_dynamic_indexed_component_array_store::id_type generic_dynamic_indexed_component_array_store::add( + std::span indices, + const std::tuple...>& component_arrays +) { + + auto component_flags = C{}; + auto min_component_count = count_type{}; + + [&](std::integer_sequence) + { + const auto& component_array = std::get(component_arrays); + if (not component_array.empty()) + { + const auto component_count = static_cast(component_array.size()); + if (min_component_count != 0 and component_count < min_component_count) + { + min_component_count = component_count; + } + component_flags |= C{ 1 } << Is; + } + } + (std::index_sequence_for{}); + + m_indices.insert(m_indices.end(), indices.begin(), indices.end()); + + [&](std::integer_sequence) + { + const auto& src_array = std::get(component_arrays); + auto& dst_array = std::get(m_component_arrays); + + if (not src_array.empty()) + { + dst_array.insert(dst_array.end(), src_array.begin(), src_array.begin() + min_component_count); + } + } + (std::index_sequence_for{}); + + m_component_flag_counts.emplace_back(component_flags, indices.size(), min_component_count); + + const auto id = id_type{ m_next_data_id.index++ }; + m_ids.push_back(id); + + return id; +} + +template +std::pair::iterator_type, bool> generic_dynamic_indexed_component_array_store::find(id_type id) +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + +template +std::pair::const_iterator, bool> generic_dynamic_indexed_component_array_store::find(id_type id) const +{ + const auto id_it = std::ranges::upper_bound(m_ids, id); + + const auto match = ( + id_it != m_ids.begin() and + *std::prev(id_it) == id + ); + + const auto index = id_it - m_ids.begin() - match; + + auto it = begin(); + it += index; + + return { it, match }; +} + + +template +void generic_dynamic_indexed_component_array_store::remove(const iterator_type& it) +{ + m_indices.erase(m_indices.begin() + it.m_offsets[0]); + + [&](std::index_sequence) + { + ([&]{ + auto& component_vector = std::get(m_component_arrays); + const auto begin = component_vector.begin() + it.m_offsets[1 + Is]; + const auto end = begin + it.m_flag_counts[it.m_index]; + component_vector.erase(begin, end); + }(), ...); + } (std::index_sequence_for{}); + + m_component_flag_counts.erase(m_component_flag_counts.begin() + it.m_index); + m_ids.erase(m_ids.begin() + it.m_index); +} + +template +void generic_dynamic_indexed_component_array_store::clear() +{ + m_indices.clear(); + [&](std::index_sequence) + { + std::get(m_component_arrays).clear(); + } (std::index_sequence_for{}); + m_component_flag_counts.clear(); + m_ids.clear(); +} + +template +typename generic_dynamic_indexed_component_array_store::iterator_type generic_dynamic_indexed_component_array_store::begin() +{ + return iterator_type{ + m_indices.data(), + component_array_ptrs(), + m_component_flag_counts.data(), + 0, + {} + }; +} + +template +typename generic_dynamic_indexed_component_array_store::iterator_type generic_dynamic_indexed_component_array_store::end() +{ + return iterator_type{ + m_indices.data(), + component_array_ptrs(), + m_component_flag_counts.data(), + m_component_flag_counts.size(), + array_counts() + }; +} + +template +typename generic_dynamic_indexed_component_array_store::const_iterator generic_dynamic_indexed_component_array_store::begin() const +{ + return iterator_type{ + m_indices.data(), + component_array_ptrs(), + m_component_flag_counts.data(), + 0, + {} + }; +} + +template +typename generic_dynamic_indexed_component_array_store::const_iterator generic_dynamic_indexed_component_array_store::end() const +{ + return iterator_type{ + m_indices.data(), + component_array_ptrs(), + m_component_flag_counts.data(), + m_component_flag_counts.size(), + array_counts() + }; +} + +template +typename generic_dynamic_indexed_component_array_store::const_iterator generic_dynamic_indexed_component_array_store::cbegin() const +{ + return const_cast(this)->begin(); +} + +template +typename generic_dynamic_indexed_component_array_store::const_iterator generic_dynamic_indexed_component_array_store::cend() const +{ + return const_cast(this)->end(); +} + +template +typename generic_dynamic_indexed_component_array_store::view_type generic_dynamic_indexed_component_array_store::view() +{ + return { begin(), end() }; +} +template +typename generic_dynamic_indexed_component_array_store::const_view_type generic_dynamic_indexed_component_array_store::view() const +{ + return { begin(), end() }; +} diff --git a/source/assets/dynamic_data_stores/generic/generic_dynamic_store.ipp b/source/assets/dynamic_data_stores/generic/generic_dynamic_store.ipp new file mode 100644 index 0000000..25380c3 --- /dev/null +++ b/source/assets/dynamic_data_stores/generic/generic_dynamic_store.ipp @@ -0,0 +1,61 @@ +#ifndef INCLUDE_GENERIC_DYNAMIC_STORE_IMPLEMENTATION +# error Never include this file directly include 'basic_dynamic_store.hpp' +#endif + +#include + +template +typename generic_dynamic_store::id_type generic_dynamic_store::add(const T& data) +{ + auto id = id_type{ m_next_data_id.index++ }; + m_data.emplace_back(data); + m_ids.emplace_back(id); + return id; +} + +template +std::pair::iterator_type, bool> generic_dynamic_store::find(id_type id) +{ + const auto it = std::ranges::upper_bound(m_ids, id); + const auto found = it != m_ids.begin() and *std::prev(it) == id; + const auto index = it - m_ids.begin() - found; + + return { m_data.begin() + index, found }; +} + +template +std::pair::const_iterator, bool> generic_dynamic_store::find(id_type id) const +{ + const auto it = std::ranges::upper_bound(m_ids, id); + const auto found = it != m_ids.begin() and *std::prev(it) == id; + const auto index = it - m_ids.begin() - found; + + return { m_data.begin() + index, found }; +} + +template +void generic_dynamic_store::remove(iterator_type it) +{ + const auto index = it - m_data.begin(); + m_data.erase(it); + m_ids.erase(m_ids.begin() + index); +} + +template +void generic_dynamic_store::clear() +{ + m_data.clear(); + m_ids.clear(); +} + +template +std::span generic_dynamic_store::data() +{ + return m_data; +} + +template +std::span generic_dynamic_store::data() const +{ + return m_data; +} diff --git a/source/assets/dynamic_read_buffers/dynamic_material_buffer.ipp b/source/assets/dynamic_read_buffers/dynamic_material_buffer.ipp new file mode 100644 index 0000000..8363407 --- /dev/null +++ b/source/assets/dynamic_read_buffers/dynamic_material_buffer.ipp @@ -0,0 +1,88 @@ +#ifndef INCLUDE_DYNAMIC_MATERIAL_DATA_IMPLEMENTATION +# error Never include this file directly include 'dynamic_material_buffer.hpp' +#endif + + +inline std::optional& dynamic_material_buffer::surface_properties() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::transparency() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::ambient_color_texture_id() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::diffuse_color_texture_id() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::specular_color_texture_id() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::shininess_texture_id() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::alpha_texture_id() +{ + return std::get(data); +} +inline std::optional& dynamic_material_buffer::bump_texture_id() +{ + return std::get& dynamic_material_buffer::surface_properties() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::transparency() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::ambient_color_texture_id() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::diffuse_color_texture_id() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::specular_color_texture_id() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::shininess_texture_id() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::alpha_texture_id() const +{ + return std::get(data); +} + +inline const std::optional& dynamic_material_buffer::bump_texture_id() const +{ + return std::get(data); +} + + +inline components::material::surface_properties& dynamic_material_buffer::initialized_surface_properties() +{ + auto& surface_properties_opt = surface_properties(); + if (not surface_properties_opt) + { + surface_properties_opt = components::material::surface_properties{}; + } + return *surface_properties_opt; +} diff --git a/source/assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp b/source/assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp new file mode 100644 index 0000000..7b1178e --- /dev/null +++ b/source/assets/dynamic_read_buffers/dynamic_mesh_buffer.ipp @@ -0,0 +1,73 @@ +#ifndef INCLUDE_DYNAMIC_MESH_DATA_IMPLEMENTATION +# error Never include this file directly include 'dynamic_mesh_buffer.hpp' +#endif + +inline std::vector& dynamic_mesh_buffer::positions() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_mesh_buffer::normals() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_mesh_buffer::tex_coords() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_mesh_buffer::colors() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_mesh_buffer::reflectances() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_mesh_buffer::triangles() +{ + return m_triangles; +} + +inline auto& dynamic_mesh_buffer::material_id() +{ + return m_material_id; +} + +inline const std::vector& dynamic_mesh_buffer::positions() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_mesh_buffer::normals() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_mesh_buffer::tex_coords() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_mesh_buffer::colors() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_mesh_buffer::reflectances() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_mesh_buffer::triangles() const +{ + return m_triangles; +} + +inline const auto& dynamic_mesh_buffer::material_id() const +{ + return m_material_id; +} diff --git a/source/assets/dynamic_read_buffers/dynamic_model_buffer.ipp b/source/assets/dynamic_read_buffers/dynamic_model_buffer.ipp new file mode 100644 index 0000000..f30be4c --- /dev/null +++ b/source/assets/dynamic_read_buffers/dynamic_model_buffer.ipp @@ -0,0 +1,148 @@ +#ifndef INCLUDE_DYNAMIC_MODEL_DATA_IMPLEMENTATION +# error Never include this file directly include 'dynamic_vertex_buffer.hpp' +#endif + +#include "util/specialised_lambda.hpp" +#include "opengl/type_utils.hpp" +#include +#include +#include + +template +C& dynamic_vertex_buffer::components() +{ + return m_components; +} + +template +const C& dynamic_vertex_buffer::components() const +{ + return m_components; +} + +template +concept numeric_type = std::integral or std::floating_point; + +template +void dynamic_vertex_buffer::build_vertex_buffer( + std::vector& vertex_buffer, + std::size_t& component_count, + std::array& component_types, + std::array& component_lengths, + GLsizei& stride +) const { + const auto for_all_components = [&](auto&& f, const T default_value) + { + return std::apply( + [&](const auto&... component_buffer) + { + std::array results{}; + auto i = std::size_t{}; + ( + ( + results[i] = [&](const auto& buffer, const auto index) -> T + { + if ((m_components & C{ 1 << index }) != C{}) + { + return f(buffer, index); + } + return default_value; + }(component_buffer, i), + ++i + ), + ... + ); + return results; + }, + m_component_buffers + ); + }; + + component_count = 0; + component_types = for_all_components( + ztu::specialised_lambda + { + [&component_count](const std::vector>&, std::size_t) + { + ++component_count; + return zgl::type_utils::to_gl_type(); + }, + [&component_count](const std::vector&, std::size_t) + { + ++component_count; + return zgl::type_utils::to_gl_type(); + } + }, + GLenum{ GL_INVALID_VALUE } + ); + + const auto element_counts = for_all_components( + [](const std::vector& buffer, std::size_t) + { + return buffer.size(); + }, + std::numeric_limits::max() + ); + + const auto minimum_element_count = std::ranges::min(element_counts); + + component_lengths = for_all_components( + ztu::specialised_lambda + { + [](const std::vector&, std::size_t) + { + return 1; + }, + [](const std::vector>&, std::size_t) + { + return Count; + } + }, + GLsizei{ 0 } + ); + + auto component_sizes = std::array{}; + for (std::size_t i{}; i != component_sizes.size(); ++i) + { + component_sizes[i] = component_lengths[i] * zgl::type_utils::size_of(component_types[i]); + } + + const auto total_size = minimum_element_count * std::accumulate( + component_sizes.begin(), + component_sizes.end(), + GLsizei{ 0 } + ); + + vertex_buffer.resize(total_size); + + // Calculate offsets and stride + auto component_offsets = component_sizes; + stride = 0; + for (std::size_t i{}; i != component_offsets.size(); ++i) { + component_offsets[i] = stride; + stride += component_sizes[i]; + } + + // Copy all the components over one by one + for_all_components( + [&](const std::vector& buffer, std::size_t index) + { + std::size_t pos = component_offsets[index]; + for (std::size_t i{}; i != minimum_element_count; ++i) + { + std::memcpy( + &vertex_buffer[pos], + buffer[i].data(), + component_sizes[index] + ); + pos += stride; + } + return 0; + }, + 0 + ); + + // remove values of unused components + std::ignore = std::ranges::remove(component_lengths, 0); + std::ignore = std::ranges::remove(component_types, GL_INVALID_VALUE); +} diff --git a/source/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp b/source/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp new file mode 100644 index 0000000..685aba8 --- /dev/null +++ b/source/assets/dynamic_read_buffers/dynamic_point_cloud_buffer.ipp @@ -0,0 +1,44 @@ +#ifndef INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION +# error Never include this file directly include 'dynamic_point_cloud_buffer.hpp' +#endif +#include "assets/components/point_cloud_vertex_components.hpp" + +inline std::vector& dynamic_point_cloud_buffer::positions() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_point_cloud_buffer::normals() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_point_cloud_buffer::colors() +{ + return std::get(vertices); +} + +inline std::vector& dynamic_point_cloud_buffer::reflectances() +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_point_cloud_buffer::positions() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_point_cloud_buffer::normals() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_point_cloud_buffer::colors() const +{ + return std::get(vertices); +} + +inline const std::vector& dynamic_point_cloud_buffer::reflectances() const +{ + return std::get(vertices); +} diff --git a/source/assets/dynamic_read_buffers/dynamic_texture_buffer.ipp b/source/assets/dynamic_read_buffers/dynamic_texture_buffer.ipp new file mode 100644 index 0000000..855117d --- /dev/null +++ b/source/assets/dynamic_read_buffers/dynamic_texture_buffer.ipp @@ -0,0 +1,151 @@ +#ifndef INCLUDE_DYNAMIC_TEXTURE_DATA_IMPLEMENTATION +# error Never include this file directly include 'dynamic_texture_buffer.hpp' +#endif + + +inline dynamic_texture_buffer::dynamic_texture_buffer( + std::unique_ptr&& data, + const dim_type width, + const dim_type height, + const components::texture::flags components +) : + m_data{ std::move(data) }, + m_width{ width }, + m_height{ height }, + m_components{ components } +{}; + +inline dynamic_texture_buffer::dynamic_texture_buffer(const dynamic_texture_buffer& other) : + m_data{ new value_type[other.component_count()] }, + m_width{ other.m_width }, + m_height{ other.m_height }, + m_components{ other.m_components } +{ + std::copy_n(other.m_data.get(), other.m_width * other.m_height, this->m_data.get()); +} + +inline dynamic_texture_buffer::dynamic_texture_buffer(dynamic_texture_buffer&& other) noexcept : + m_data{ std::move(other.m_data) }, + m_width{ other.m_width }, + m_height{ other.m_height }, + m_components{ other.m_components } +{ + other.m_width = 0; + other.m_height = 0; + other.m_components = components::texture::flags::none; +} + +inline dynamic_texture_buffer& dynamic_texture_buffer::operator=(const dynamic_texture_buffer& other) +{ + if (this != &other) [[likely]] + { + + const auto m_size = this->component_count(); + const auto o_size = other.component_count(); + + if (o_size > m_size) { + this->~dynamic_texture_buffer(); + this->m_data.reset(new value_type[o_size]); + } + + std::copy_n(other.m_data.get(), o_size, this->m_data.get()); + + this->m_width = other.m_width; + this->m_height = other.m_height; + this->m_components = other.m_components; + } + + return *this; +} + +inline dynamic_texture_buffer& dynamic_texture_buffer::operator=(dynamic_texture_buffer&& other) noexcept +{ + if (this != &other) [[likely]] + { + this->~dynamic_texture_buffer(); + + this->m_data = std::move(other.m_data); + this->m_width = other.m_width; + this->m_height = other.m_height; + + other.m_width = 0; + other.m_height = 0; + other.m_components = components::texture::flags::none; + } + return *this; +} + +inline components::texture::flags dynamic_texture_buffer::components() const +{ + return m_components; +} + +inline dynamic_texture_buffer::dim_type dynamic_texture_buffer::width() const +{ + return m_width; +} + +inline dynamic_texture_buffer::dim_type dynamic_texture_buffer::height() const +{ + return m_height; +} + +inline std::pair dynamic_texture_buffer::dimensions() const +{ + return { m_width, m_height }; +} + +inline dynamic_texture_buffer::size_type dynamic_texture_buffer::pixel_count() const +{ + return static_cast(m_width) * static_cast(m_height); +} + +inline dynamic_texture_buffer::size_type dynamic_texture_buffer::component_count() const +{ + return std::popcount(static_cast>(m_components)); +} + +inline dynamic_texture_buffer::size_type dynamic_texture_buffer::size() const +{ + return pixel_count() * component_count(); +} + +inline dynamic_texture_buffer::const_pointer dynamic_texture_buffer::data() const +{ + return m_data.get(); +} + +inline dynamic_texture_buffer::pointer dynamic_texture_buffer::data() +{ + return m_data.get(); +} + +inline dynamic_texture_buffer::const_iterator dynamic_texture_buffer::begin() const +{ + return data(); +} + +inline dynamic_texture_buffer::iterator dynamic_texture_buffer::begin() +{ + return data(); +} + +inline dynamic_texture_buffer::const_iterator dynamic_texture_buffer::end() const +{ + return begin() + component_count(); +} + +inline dynamic_texture_buffer::iterator dynamic_texture_buffer::end() +{ + return begin() + component_count(); +} + +inline dynamic_texture_buffer::const_iterator dynamic_texture_buffer::cbegin() const +{ + return const_cast(begin()); +} + +inline dynamic_texture_buffer::const_iterator dynamic_texture_buffer::cend() const +{ + return const_cast(begin()); +} diff --git a/source/assets/prefetch_lookups/pose_prefetch_lookup.cpp b/source/assets/prefetch_lookups/pose_prefetch_lookup.cpp new file mode 100644 index 0000000..df551c6 --- /dev/null +++ b/source/assets/prefetch_lookups/pose_prefetch_lookup.cpp @@ -0,0 +1,139 @@ +#include "assets/prefetch_lookups/pose_prefetch_lookup.hpp" + +#include + + pose_prefetch_lookup::find_directory( + const std::filesystem::path& directory +) { + const auto dir_it = m_directory_lookup.find(directory); + const auto dir_match = dir_it != m_directory_lookup.end(); + auto offset = index_type{}; + + if (dir_match) + { + for (index_type i{}; i != dir_match; ++i) + { + offset += m_directory_indices[offset]; + } + } + + return { std::make_pair(dir_it, offset), dir_match }; +} + +std::pair pose_prefetch_lookup::find_index( + directory_iterator directory_it, + index_type index +) { + const auto [ dir_it, dir_offset ] = directory_it; + + const auto index_count = m_directory_indices[dir_offset]; + const auto indices_begin = m_directory_indices.begin() + dir_offset + 1; + const auto indices_end = indices_begin + index_count; + + const auto it = std::upper_bound(indices_begin, indices_end, index); + const auto is_match = it != indices_begin and *std::prev(it) == index; + + auto match = dynamic_pose_store::id_type{}; + + if (is_match) + { + const auto dir_index = dir_it->second; + const auto id_index = it - m_directory_indices.begin() - match - dir_index; + match = m_pose_ids[id_index]; + } + + return { it, match }; +} + +pose_prefetch_lookup::directory_iterator pose_prefetch_lookup::emplace_dir( + directory_iterator directory_it, + const std::filesystem::path& directory +) { + auto [ dir_it, dir_offset ] = directory_it; + + dir_it = m_directory_lookup.emplace_hint(dir_it, directory, m_directory_lookup.size()); + dir_offset = m_directory_indices.size(); + m_directory_indices.push_back(1); + + return { dir_it, dir_offset }; +} + +void pose_prefetch_lookup::emplace( + const std::filesystem::path& directory, + index_type index, + dynamic_pose_store::id_type id +) { + auto [ directory_it, dir_match ] = find_directory(directory); + + if (not dir_match) [[unlikely]] + { + directory_it = emplace_dir(directory_it, directory); + } + + const auto [ index_it, match ] = find_index( + directory_it, + index + ); + + if (not match) + { + + } + + emplace_hint_dir_index( + directory_it, + index_it, + index, + id + ); +} + + +void pose_prefetch_lookup::emplace_hint_dir( + directory_iterator directory_it, + const std::filesystem::path& directory, + const index_type index, + const dynamic_pose_store::id_type id +) { + + directory_it = emplace_dir(directory_it, directory); + + const auto [ index_it, match ] = find_index( + directory_it, + index + ); + + if (match) [[unlikely]] + { + const auto dir_index = dir_it->second; + const auto id_index = index_it - m_directory_indices.begin() - dir_index; + m_pose_ids[id_index] = match; // TODO I guess I should warn?!? + } + else [[likely]] + { + emplace_hint_dir_index( + directory_it, + index_it, + index, + id + ); + } +} + +void pose_prefetch_lookup::emplace_hint_dir_index( + directory_iterator directory_it, + index_iterator index_it, + index_type index, + const dynamic_pose_store::id_type id +) { + const auto [ dir_it, dir_offset ] = directory_it; + const auto dir_index = dir_it->second; + + index_it = m_directory_indices.emplace(index_it, index); + ++m_directory_indices[dir_offset]; + + const auto id_index = index_it - m_directory_indices.begin() - dir_index; + const auto id_it = m_pose_ids.begin() + id_index; + + m_pose_ids.insert(id_it, id); +} \ No newline at end of file diff --git a/source/geometry/normal_estimation.cpp b/source/geometry/normal_estimation.cpp new file mode 100644 index 0000000..4b279d9 --- /dev/null +++ b/source/geometry/normal_estimation.cpp @@ -0,0 +1,82 @@ +#include "geometry/normal_estimation.hpp" + +#include +#include + +void estimate_normals( + std::span vertices, + std::span> triangles, + std::vector& normals +) { + + normals.resize(vertices.size()); + + std::ranges::fill(normals, std::array{ 0.0f, 0.0f, 0.0f }); + + for (const auto& triangle : triangles) + { + auto abc = std::array{}; + + std::ranges::transform( + triangle, + abc.begin(), + [&](const auto& index) + { + const auto& [ x, y, z ] = vertices[index]; + return glm::vec3{ x, y, z }; + } + ); + + const auto [ A, B, C ] = abc; + + // TODO normalization can be done more efficiently + const auto normal = glm::normalize(glm::vec3{ + A.y * B.z - A.z * B.y, + A.z * B.x - A.x * B.z, + A.x * B.y - A.y * B.x + }); + + const auto a_length = glm::length(B - C); + const auto b_length = glm::length(C - A); + const auto c_length = glm::length(A - B); + + const auto area = 0.25f * std::sqrt( + (a_length + b_length + c_length) * + (-a_length + b_length + c_length) * + (a_length - b_length + c_length) * + (a_length + b_length - c_length) + ); + + const auto weighted_normal = normal * area; + + for (const auto& index : triangle) + { + auto& normal_avg = normals[index]; + for (int i{}; i != 3; ++i) + { + normal_avg[i] += weighted_normal[i]; + } + } + } + + using normal_component_type = components::mesh_vertex::normal::component_type; + constexpr auto epsilon = std::numeric_limits::epsilon(); + + for (auto& [ x, y, z ] : normals) + { + const auto length = glm::length(glm::vec3{ x, y, z }); + if (length <= epsilon) + { + x = 0; + y = 1; + z = 0; + } + else + { + const auto scale = 1.0f / length; + x *= scale; + y *= scale; + z *= scale; + } + } +} diff --git a/source/opengl/data/material_data.ipp b/source/opengl/data/material_data.ipp new file mode 100644 index 0000000..a02d205 --- /dev/null +++ b/source/opengl/data/material_data.ipp @@ -0,0 +1,129 @@ +#ifndef INCLUDE_MATERIAL_DATA_IMPLEMENTATION +# error Never include this file directly include 'material_data.hpp' +#endif + +namespace zgl +{ +inline material_data::material_data( + const std::optional& texture_handle, + const std::optional& surface_properties_handle, + const std::optional& alpha_handle, + std::optional&& texture_data, + const material_component::flags components +) : + m_handle{ + .texture = texture_handle, + .surface_properties = surface_properties_handle, + .alpha = alpha_handle + }, + m_texture_data{ std::move(texture_data) }, + m_component_types{ components } {} + +inline material_data::material_data(material_data&& other) noexcept +{ + m_handle = other.m_handle; + m_texture_data = std::move(other.m_texture_data); + m_component_types = other.m_component_types; + + other.m_handle.texture = std::nullopt; + other.m_handle.surface_properties = std::nullopt; + other.m_handle.alpha = std::nullopt; + other.m_component_types = material_component::flags::none; +} + +inline material_data& material_data::operator=(material_data&& other) noexcept +{ + if (&other != this) + { + this->~material_data(); + + m_handle = other.m_handle; + m_texture_data = std::move(other.m_texture_data); + m_component_types = other.m_component_types; + + other.m_handle.texture = std::nullopt; + other.m_handle.surface_properties = std::nullopt; + other.m_handle.alpha = std::nullopt; + other.m_component_types = material_component::flags::none; + } + + return *this; +} + + +inline material_handle material_data::handle() const +{ + return m_handle; +} + +inline material_component::flags material_data::components() const +{ + return m_component_types; +} + + +inline std::error_code material_data::build_from( + const std::optional& texture_opt, + const std::optional& surface_properties_opt, + const std::optional& transparency_opt, + const material_component::flags components, + material_data& dst_data +) { + + auto texture_data_opt = std::optional{ std::nullopt }; + auto texture_handle_opt = std::optional{ std::nullopt }; + if (texture_opt) + { + const auto& texture = *texture_opt; + auto texture_data = zgl::texture_data{}; + + if (const auto e = texture_data::build_from( + std::span(texture.cbegin(), texture.cend()), + GL_RGBA, GL_UNSIGNED_BYTE, + texture.width(), + texture.height(), + texture_data + )) { + return e; + } + + texture_handle_opt.emplace(texture_data.handle()); + texture_data_opt.emplace(std::move(texture_data)); + } + + auto surface_properties_data_opt = std::optional{ std::nullopt }; + if (surface_properties_opt) + { + const auto& [ ambient, diffuse, specular, shininess ] = *surface_properties_opt; + surface_properties_data_opt.emplace( + glm::vec3{ ambient[0], ambient[1], ambient[2] }, + glm::vec3{ diffuse[0], diffuse[1], diffuse[2] }, + glm::vec3{ specular[0], specular[1], specular[2] }, + shininess + ); + } + + auto alpha_data_opt = std::optional{ std::nullopt }; + if (transparency_opt) + { + alpha_data_opt.emplace(1.0f - *transparency_opt); + } + + + /*dst_data = material_data{ + texture_handle_opt, + surface_properties_data_opt, + alpha_data_opt, + std::move(texture_data_opt), + components + };*/ + + dst_data.m_handle.texture = texture_handle_opt; + dst_data.m_handle.surface_properties = surface_properties_data_opt; + dst_data.m_handle.alpha = alpha_data_opt; + dst_data.m_texture_data = std::move(texture_data_opt); + dst_data.m_component_types = components; + + return {}; +} +} diff --git a/source/opengl/data/mesh_data.cpp b/source/opengl/data/mesh_data.cpp new file mode 100644 index 0000000..48e1075 --- /dev/null +++ b/source/opengl/data/mesh_data.cpp @@ -0,0 +1,119 @@ +#include "opengl/data/mesh_data.hpp" + +#include "opengl/type_utils.hpp" +#include "opengl/error.hpp" + +#include "GL/glew.h" +#include +#include "util/logger.hpp" // TODO remove + + +std::error_code zgl::mesh_data::build_from( + const std::span vertex_buffer, + const std::span component_types, + const std::span component_lengths, + const GLsizei stride, + const std::span index_buffer, + const ztu::u32 material_id, + const components::mesh_vertex::flags components, + mesh_data& data +) { + if (not std::ranges::all_of(component_types, type_utils::is_valid_type)) + { + ztu::logger::debug("not all types valid."); + return std::make_error_code(std::errc::invalid_argument); + } + + const auto vertices_byte_count = vertex_buffer.size() * sizeof(std::uint8_t); + if (vertices_byte_count > std::numeric_limits::max()) + { + return std::make_error_code(std::errc::value_too_large); + } + + const auto indices_byte_count = index_buffer.size() * sizeof(ztu::u32); + if (indices_byte_count > std::numeric_limits::max()) + { + return std::make_error_code(std::errc::value_too_large); + } + + auto error = std::error_code{}; + + auto check_error = [&error, ec = static_cast(GL_NO_ERROR)]() mutable -> const std::error_code& + { + ec = glGetError(); + if (ec != GL_NO_ERROR) + { + error = make_error_code(ec); + } + return error; + }; + + GLuint vao_id; + glGenVertexArrays(1, &vao_id); + glBindVertexArray(vao_id); + if (check_error()) return error; + + ztu::logger::debug("Created mesh vao: % valid: %", vao_id, (bool)glIsVertexArray(vao_id)); + + GLuint vertex_buffer_id; + glGenBuffers(1, &vertex_buffer_id); + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(vertices_byte_count), + vertex_buffer.data(), + GL_STATIC_DRAW + ); + if (check_error()) return error; + + GLuint index_buffer_id; + glGenBuffers(1, &index_buffer_id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + static_cast(indices_byte_count), + index_buffer.data(), + GL_STATIC_DRAW + ); + if (check_error()) return error; + + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); + if (check_error()) return error; + + auto offset = GLsizei{ 0 }; + + for (std::size_t i{}; i != component_types.size(); ++i) + { + const auto type = component_types[i]; + const auto length = component_lengths[i]; + const auto byte_count = type_utils::size_of(type); + + glVertexAttribPointer( + i, + length, + type, + GL_FALSE, + stride, + reinterpret_cast(offset) + ); + + glEnableVertexAttribArray(i); + if (check_error()) return error; + + offset += length * byte_count; + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + data = mesh_data( + vertex_buffer_id, + index_buffer_id, + vao_id, + material_id, + components, + index_buffer.size() + ); + + return {}; +} diff --git a/source/opengl/data/mesh_data.ipp b/source/opengl/data/mesh_data.ipp new file mode 100644 index 0000000..152112f --- /dev/null +++ b/source/opengl/data/mesh_data.ipp @@ -0,0 +1,90 @@ +#ifndef INCLUDE_MESH_DATA_IMPLEMENTATION +# error Never include this file directly include 'mesh_data.hpp' +#endif + +namespace zgl +{ +inline mesh_data::mesh_data( + const GLuint vertex_vbo_id, + const GLuint index_vbo_id, + const GLuint vao_id, + const ztu::u32 material_id, + const components::mesh_vertex::flags components, + const GLsizei index_count +) : + m_handle{ + .vao_id = vao_id, + .index_count = index_count + }, + m_vertex_vbo_id{ vertex_vbo_id }, + m_index_vbo_id{ index_vbo_id }, + m_material_id{ material_id }, + m_component_types{ components } {} + +inline mesh_data& mesh_data::operator=(mesh_data&& other) noexcept +{ + if (&other != this) + { + this->~mesh_data(); + + m_handle = other.m_handle; + m_vertex_vbo_id = other.m_vertex_vbo_id; + m_index_vbo_id = other.m_index_vbo_id; + m_material_id = other.m_material_id; + m_component_types = other.m_component_types; + + other.m_handle.vao_id = 0; + other.m_handle.index_count = 0; + other.m_vertex_vbo_id = 0; + other.m_index_vbo_id = 0; + other.m_material_id = 0; + other.m_component_types = components::mesh_vertex::flags::none; + } + + return *this; +} + +inline mesh_data::mesh_data(mesh_data&& other) noexcept : + m_handle{ other.m_handle }, + m_vertex_vbo_id{ other.m_vertex_vbo_id }, + m_index_vbo_id{ other.m_index_vbo_id }, + m_material_id{ other.m_material_id }, + m_component_types{ other.m_component_types } +{ + other.m_handle.vao_id = 0; + other.m_handle.index_count = 0; + other.m_vertex_vbo_id = 0; + other.m_index_vbo_id = 0; + other.m_material_id = 0; + other.m_component_types = components::mesh_vertex::flags::none; +} + +inline mesh_data::~mesh_data() { + if (m_vertex_vbo_id) { + glDeleteBuffers(1, &m_vertex_vbo_id); + } + if (m_index_vbo_id) { + glDeleteBuffers(1, &m_index_vbo_id); + } + if (m_handle.vao_id) { + glDeleteVertexArrays(1, &m_handle.vao_id); + } +} + + +inline mesh_handle mesh_data::handle() const +{ + return m_handle; +} + +inline components::mesh_vertex::flags mesh_data::components() const +{ + return m_component_types; +} + +inline ztu::u32 mesh_data::material_id() const +{ + return m_material_id; +} + +} diff --git a/source/opengl/data/point_cloud_data.cpp b/source/opengl/data/point_cloud_data.cpp new file mode 100644 index 0000000..73cfa98 --- /dev/null +++ b/source/opengl/data/point_cloud_data.cpp @@ -0,0 +1,99 @@ +#include "opengl/data/point_cloud_data.hpp" + +#include "opengl/type_utils.hpp" +#include "opengl/error.hpp" + +#include +#include "GL/glew.h" + +std::error_code zgl::point_cloud_data::build_from( + std::span point_buffer, + std::span component_types, + std::span component_lengths, + GLsizei stride, + point_cloud_data& data +) { + if (not std::ranges::all_of(component_types, type_utils::is_valid_type)) + { + return std::make_error_code(std::errc::invalid_argument); + } + + const auto points_byte_count = point_buffer.size() * sizeof(std::uint8_t); + if (points_byte_count > std::numeric_limits::max()) + { + return std::make_error_code(std::errc::value_too_large); + } + + auto error = std::error_code{}; + + auto check_error = [&error, ec = static_cast(GL_NO_ERROR)]() mutable -> const std::error_code& + { + ec = glGetError(); + if (ec != GL_NO_ERROR) { + error = make_error_code(ec); + } + return error; + }; + + ztu::u32 vao_id; + glGenVertexArrays(1, &vao_id); + if (check_error()) return error; + + glBindVertexArray(vao_id); + if (check_error()) return error; + + ztu::u32 vertex_buffer_id; + glGenBuffers(1, &vertex_buffer_id); + if (check_error()) return error; + + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); + if (check_error()) return error; + + glBufferData( + GL_ARRAY_BUFFER, + static_cast(points_byte_count), + point_buffer.data(), + GL_STATIC_DRAW + ); + if (check_error()) return error; + + + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); + if (check_error()) return error; + + auto offset = GLsizei{ 0 }; + + for (std::size_t i{}; i != component_types.size(); ++i) + { + const auto type = component_types[i]; + const auto length = component_lengths[i]; + const auto byte_count = type_utils::size_of(type); + + glVertexAttribPointer( + i, + length, + type, + GL_FALSE, + stride, + reinterpret_cast(offset) + ); + if (check_error()) return error; + glEnableVertexAttribArray(i); + if (check_error()) return error; + + offset += length * byte_count; + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + if (check_error()) return error; + glBindVertexArray(0); + if (check_error()) return error; + + data = point_cloud_data( + vertex_buffer_id, + vao_id, + point_buffer.size() + ); + + return {}; +} diff --git a/source/opengl/data/point_cloud_data.ipp b/source/opengl/data/point_cloud_data.ipp new file mode 100644 index 0000000..6268a54 --- /dev/null +++ b/source/opengl/data/point_cloud_data.ipp @@ -0,0 +1,66 @@ +#ifndef INCLUDE_POINT_CLOUD_DATA_IMPLEMENTATION +# error Never include this file directly include 'point_cloud_data.hpp' +#endif + + +namespace zgl +{ +inline point_cloud_data::point_cloud_data( + const GLuint vertex_vbo_id, + const GLuint vao_id, + const GLsizei point_count +) : + m_handle{ + .vao_id = vao_id, + .point_count = point_count + }, + m_vertex_vbo_id{ vertex_vbo_id } {} + +inline point_cloud_data::point_cloud_data(point_cloud_data&& other) noexcept : + m_handle{ other.m_handle }, + m_vertex_vbo_id{ other.m_vertex_vbo_id } +{ + other.m_handle.vao_id = 0; + other.m_handle.point_count = 0; + other.m_vertex_vbo_id = 0; +} + +inline point_cloud_data& point_cloud_data::operator=(point_cloud_data&& other) noexcept +{ + if (&other != this) + { + this->~point_cloud_data(); + + m_handle = other.m_handle; + m_vertex_vbo_id = other.m_vertex_vbo_id; + + other.m_handle.vao_id = 0; + other.m_handle.point_count = 0; + other.m_vertex_vbo_id = 0; + } + + return *this; +} + +inline point_cloud_data::~point_cloud_data() +{ + if (m_vertex_vbo_id) + { + glDeleteBuffers(1, &m_vertex_vbo_id); + } + if (m_handle.vao_id) + { + glDeleteVertexArrays(1, &m_handle.vao_id); + } +} + +inline point_cloud_handle point_cloud_data::handle() const +{ + return m_handle; +} + +inline components::point_cloud_vertex::flags point_cloud_data::components() const +{ + return m_component_types; +} +} diff --git a/source/opengl/data/shader_data.cpp b/source/opengl/data/shader_data.cpp new file mode 100755 index 0000000..48afaa3 --- /dev/null +++ b/source/opengl/data/shader_data.cpp @@ -0,0 +1,46 @@ +#include "opengl/data/shader_data.hpp" + +#include "GL/glew.h" +#include "opengl/error.hpp" +#include "util/logger.hpp" + +namespace zgl +{ +std::error_code shader_data::build_from( + const GLenum type, + const std::string& source, + shader_data& data +) { + auto shader_id = GLuint{ 0 }; + + if (not source.empty()) + { + shader_id = glCreateShader(type); + + // Curious choice to take lists as parameters... + const auto first_list_element = source.c_str(); + const auto first_list_element_len = static_cast(source.length()); + glShaderSource(shader_id, 1, &first_list_element, &first_list_element_len); + glCompileShader(shader_id); + + GLint success; + glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success); + if (not success) + { + GLint log_length{}; + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length); + + auto log = std::string(log_length, ' '); + glGetShaderInfoLog(shader_id, log_length, nullptr, log.data()); + + ztu::logger::warn("Error while compiling shader:\n%", log); + + return std::make_error_code(std::errc::invalid_argument); + } + } + + data = shader_data{ shader_id, type }; + + return {}; +} +} diff --git a/source/opengl/data/shader_data.ipp b/source/opengl/data/shader_data.ipp new file mode 100644 index 0000000..d9fb7b6 --- /dev/null +++ b/source/opengl/data/shader_data.ipp @@ -0,0 +1,47 @@ +#ifndef INCLUDE_SHADER_DATA_IMPLEMENTATION +# error Never include this file directly include 'shader_data.hpp' +#endif + +namespace zgl +{ +inline shader_data::shader_data(const GLuint shader_id, const GLenum type) + : m_handle{ shader_id }, m_type{ type } {} + + +inline shader_data::shader_data(shader_data&& other) noexcept +{ + m_handle = other.m_handle; + m_type = other.m_type; + other.m_handle.shader_id = 0; + other.m_type = GL_INVALID_ENUM; +} + +inline shader_data& shader_data::operator=(shader_data&& other) noexcept +{ + if (&other != this) + { + this->~shader_data(); + + m_handle = other.m_handle; + m_type = other.m_type; + other.m_handle.shader_id = 0; + other.m_type = GL_INVALID_ENUM; + } + return *this; +} + +inline shader_data::~shader_data() +{ + if (m_handle.shader_id) + { + glDeleteShader(m_handle.shader_id); + } + m_type = GL_INVALID_ENUM; +} + +inline shader_handle shader_data::handle() const +{ + return m_handle; +} + +} diff --git a/source/opengl/data/shader_program_data.cpp b/source/opengl/data/shader_program_data.cpp new file mode 100755 index 0000000..84c4ebb --- /dev/null +++ b/source/opengl/data/shader_program_data.cpp @@ -0,0 +1,90 @@ + +#include "opengl/data/shader_program_data.hpp" + +#include +#include "GL/glew.h" +#include + +#include "util/for_each.hpp" +#include "util/logger.hpp" +#include "opengl/error.hpp" + +namespace zgl +{ + +std::error_code shader_program_data::build_from( + const shader_handle& vertex_shader, + const shader_handle& geometry_shader, + const shader_handle& fragment_shader, + shader_program_data& data +) { + auto error = std::error_code{}; + + auto check_error = [&error, ec = static_cast(GL_NO_ERROR)]() mutable -> std::error_code& + { + ec = glGetError(); + if (ec != GL_NO_ERROR) { + error = make_error_code(ec); + } + return error; + }; + + const auto program_id = glCreateProgram(); + if (check_error()) return error; + + using namespace std::string_view_literals; + + constexpr auto shader_names = std::array{ + "vertex"sv, "geometry"sv, "fragment"sv + }; + + for (const auto& [shader, name] : { + std::tie(vertex_shader, shader_names[0]), + std::tie(geometry_shader, shader_names[1]), + std::tie(fragment_shader, shader_names[2]) + }) { + if (shader.shader_id) { + glAttachShader(program_id, shader.shader_id); + } else { + ztu::logger::warn("Using default % shader", name); + } + } + + glLinkProgram(program_id); + if (check_error()) return error; + + auto status = GLint{ GL_FALSE }; + glGetProgramiv(program_id, GL_LINK_STATUS, &status); + if (check_error()) return error; + + if (status == GL_FALSE) { + GLint log_length{}; + glGetShaderiv(program_id, GL_INFO_LOG_LENGTH, &log_length); + + auto log = std::string(log_length, ' '); + glGetProgramInfoLog(program_id, log_length, nullptr, log.data()); + + ztu::logger::warn("Error while linking program: [%] %", log_length, log); + + return std::make_error_code(std::errc::io_error); + } + + + glUseProgram(0); + + ztu::for_each::argument( + [&](const auto& shader) + { + if (shader.shader_id) { + glDetachShader(program_id, shader.shader_id); + } + return true; + }, + vertex_shader, geometry_shader, fragment_shader + ); + + data = shader_program_data{ program_id }; + + return {}; +} +} diff --git a/source/opengl/data/shader_program_data.ipp b/source/opengl/data/shader_program_data.ipp new file mode 100755 index 0000000..3389ca7 --- /dev/null +++ b/source/opengl/data/shader_program_data.ipp @@ -0,0 +1,45 @@ +#ifndef INCLUDE_SHADER_PROGRAM_DATA_IMPLEMENTATION +# error Never include this file directly include 'shader_program_data.hpp' +#endif + +#include + +#include "util/for_each.hpp" +#include "util/logger.hpp" +#include "opengl/error.hpp" + +namespace zgl +{ +inline shader_program_data::shader_program_data(GLuint program_id) + : m_handle{ program_id } {} + + +inline shader_program_data::shader_program_data(shader_program_data&& other) noexcept +{ + m_handle = other.m_handle; + other.m_handle.program_id = 0; +} + +inline shader_program_data& shader_program_data::operator=(shader_program_data&& other) noexcept +{ + if (&other != this) + { + this->~shader_program_data(); + m_handle = other.m_handle; + other.m_handle.program_id = 0; + } + return *this; +} + +inline shader_program_data::~shader_program_data() +{ + if (m_handle.program_id) { + glDeleteProgram(m_handle.program_id); + } +} + +[[nodiscard]] inline shader_program_handle shader_program_data::handle() const +{ + return m_handle; +} +} diff --git a/source/opengl/data/texture_data.ipp b/source/opengl/data/texture_data.ipp new file mode 100644 index 0000000..c1d44c8 --- /dev/null +++ b/source/opengl/data/texture_data.ipp @@ -0,0 +1,79 @@ +#ifndef INCLUDE_TEXTURE_DATA_IMPLEMENTATION +# error Never include this file directly include 'texture_data.hpp' +#endif + +namespace zgl +{ +inline texture_data::texture_data(const GLuint texture_id) + : m_handle{ texture_id } {} + +inline texture_data::texture_data(texture_data&& other) noexcept + : m_handle{ other.m_handle } +{ + other.m_handle.texture_id = 0; +} + +inline texture_data& texture_data::operator=(texture_data&& other) noexcept +{ + if (&other != this) + { + this->~texture_data(); + + m_handle = other.m_handle; + + other.m_handle.texture_id = 0; + } + + return *this; +} + +template +std::error_code texture_data::build_from( + std::span buffer, + const GLenum format, + const GLenum type, + const GLsizei width, + const GLsizei height, + texture_data& data +) { + GLuint texture_id; + + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexImage2D( + GL_TEXTURE_2D, 0, + GL_RGBA8, + width, + height, + 0, + format, type, + buffer.data() + ); + glGenerateMipmap(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + + data = texture_data(texture_id); + + return {}; +} + +inline texture_data::~texture_data() +{ + if (m_handle.texture_id) + { + glDeleteTextures(1, &m_handle.texture_id); + } +} + +inline texture_handle texture_data::handle() const +{ + return { m_handle.texture_id }; +} +} diff --git a/source/opengl/handles/shader_program_handle.cpp b/source/opengl/handles/shader_program_handle.cpp new file mode 100644 index 0000000..589c813 --- /dev/null +++ b/source/opengl/handles/shader_program_handle.cpp @@ -0,0 +1,152 @@ +#include "opengl/handles/shader_program_handle.hpp" + +#include +// TODO remove +#include "opengl/error.hpp" +#include "util/logger.hpp" + +namespace zgl +{ + +shader_program_handle::attribute_support_type shader_program_handle::check_attribute_support( + const std::span attributes +) const { + auto attribute_candidates = attribute_support_type{}; + + auto error = std::error_code{}; + auto check_error = [&error, ec = static_cast(GL_NO_ERROR)]() mutable -> std::error_code& + { + ec = glGetError(); + if (ec != GL_NO_ERROR) { + error = make_error_code(ec); + } + return error; + }; + + auto curr_attribute_flag = attribute_support_type{ 1 }; + for (const auto& attribute : attributes) { + const auto location = glGetAttribLocation(program_id, attribute.name); + if (location == attribute.info.location) + { + attribute_candidates |= curr_attribute_flag; + } + curr_attribute_flag <<= 1; + } + + auto supported_attributes = attribute_support_type{}; + + GLint count; + glGetProgramiv(program_id, GL_ACTIVE_ATTRIBUTES, &count); + if (check_error()) ztu::logger::error("GL_err: %", error.message()); + + for (GLint i{}; i != count and attribute_candidates; ++i) + { + GLenum type; + GLint size; + GLsizei name_length; + auto name = std::array{}; + glGetActiveAttrib( + program_id, i, + name.size(), + &name_length, + &size, &type, + name.data() + ); + if (check_error()) ztu::logger::error("GL_err: %", error.message()); + + const auto name_view = std::string_view(name.data(), name_length); + + auto attribute_index = attribute_support_type{}; + for (auto candidates = attribute_candidates; candidates; candidates >>= 1) { + if (candidates & 1) + { + const auto& attribute = attributes[attribute_index]; + if (type == attribute.info.type and name_view == attribute.name) + { + const auto new_uniform_flag = attribute_support_type{ 1 } << attribute_index; + supported_attributes |= new_uniform_flag; + attribute_candidates ^= new_uniform_flag; + } + } + ++attribute_index; + } + } + + return supported_attributes; +} + +shader_program_handle::uniform_support_type shader_program_handle::check_uniform_support( + const std::span uniforms +) const { + auto uniform_candidates = uniform_support_type{}; + + auto error = std::error_code{}; + auto check_error = [&error, ec = static_cast(GL_NO_ERROR)]() mutable -> std::error_code& + { + ec = glGetError(); + if (ec != GL_NO_ERROR) { + error = make_error_code(ec); + } + return error; + }; + + auto curr_uniform_flag = uniform_support_type{ 1 }; + for (const auto& uniform : uniforms) + { + const auto location = glGetUniformLocation(program_id, uniform.name); + + if (location == uniform.info.location) + { + uniform_candidates |= curr_uniform_flag; + ztu::logger::debug("[%] '%': %.", program_id, uniform.name, location); + } + else + { + ztu::logger::debug("Expected '%' at % but was %.", uniform.name, uniform.info.location, location); + } + curr_uniform_flag <<= 1; + } + + auto supported_uniforms = uniform_support_type{}; + + GLint count; + glGetProgramiv(program_id, GL_ACTIVE_UNIFORMS, &count); + if (check_error()) ztu::logger::error("GL_err: %", error.message()); + + for (GLint i{}; i != count and uniform_candidates; ++i) + { + GLenum type; + GLint size; + GLsizei name_length; + auto name = std::array{}; + glGetActiveUniform( + program_id, i, + name.size(), + &name_length, + &size, &type, + name.data() + ); + if (check_error()) ztu::logger::error("GL_err: %", error.message()); + + const auto name_view = std::string_view(name.data(), name_length); + + auto uniform_index = uniform_support_type{}; + for (auto candidates = uniform_candidates; candidates; candidates >>= 1) + { + if (candidates & 1) + { + const auto& uniform = uniforms[uniform_index]; + if (type == uniform.info.type and name_view == uniform.name) + { + const auto new_uniform_flag = uniform_support_type{ 1 } << uniform_index; + supported_uniforms |= new_uniform_flag; + uniform_candidates ^= new_uniform_flag; + } + } + ++uniform_index; + } + } + + return supported_uniforms; +} +} diff --git a/source/opengl/shader_program_lookup.cpp b/source/opengl/shader_program_lookup.cpp new file mode 100644 index 0000000..fd9633a --- /dev/null +++ b/source/opengl/shader_program_lookup.cpp @@ -0,0 +1,195 @@ +#include "opengl/shader_program_lookup.hpp" + +#include +#include + +#include "util/logger.hpp" // TODO remove + +namespace zgl +{ + + +void shader_program_lookup::add( + const shader_program_handle& shader_program_handle, + const std::span all_attributes, + const std::span all_uniforms +) { + const auto attributes = shader_program_handle.check_attribute_support(all_attributes); + const auto uniforms = shader_program_handle.check_uniform_support(all_uniforms); + + ztu::logger::debug("add [%] uniforms: % attributes: %", + shader_program_handle.program_id, + std::bitset<32>(uniforms), + std::bitset<32>(attributes) + ); + + const auto lower_uniform = std::ranges::lower_bound( + m_mesh_shader_program_uniforms, + uniforms + ); + + const auto upper_uniform = std::find_if( + lower_uniform, m_mesh_shader_program_uniforms.end(), + [&](const auto& curr_uniforms) { + return curr_uniforms > uniforms; + } + ); + + const auto lower_index = lower_uniform - m_mesh_shader_program_uniforms.begin(); + const auto upper_index = upper_uniform - m_mesh_shader_program_uniforms.begin(); + + const auto lower_attribute = m_mesh_shader_program_attributes.begin() + lower_index; + const auto upper_attribute = m_mesh_shader_program_attributes.begin() + upper_index; + + const auto attribute_it = std::upper_bound( + lower_attribute, upper_attribute, + attributes, + [](const auto& attributes, const auto& entry) { + return attributes < entry.attributes; + } + ); + + const auto index = attribute_it - m_mesh_shader_program_attributes.begin(); + + const auto attribute_locations = attribute_location_flags( + attributes, all_attributes + ); + + m_mesh_shader_program_uniforms.insert( + m_mesh_shader_program_uniforms.begin() + index, uniforms + ); + + m_mesh_shader_program_attributes.emplace( + attribute_it, attributes, attribute_locations + ); + + m_mesh_shader_programs.insert( + m_mesh_shader_programs.begin() + index, shader_program_handle + ); +} + + +std::optional shader_program_lookup::find( + shader_program_handle::attribute_support_type attributes, + shader_program_handle::uniform_support_type uniforms, + const std::span all_attributes +) const { + + const auto lower_uniform = std::ranges::lower_bound( + m_mesh_shader_program_uniforms, + uniforms + ); + + if ( + lower_uniform == m_mesh_shader_program_uniforms.end() or + *lower_uniform != uniforms + ) { + return std::nullopt; + } + + const auto upper_uniform = std::find_if( + lower_uniform, m_mesh_shader_program_uniforms.end(), + [&](const auto& curr_uniforms) { + return curr_uniforms > uniforms; + } + ); + + const auto lower_index = lower_uniform - m_mesh_shader_program_uniforms.begin(); + const auto upper_index = upper_uniform - m_mesh_shader_program_uniforms.begin(); + + const auto relevant_attributes = std::span( + m_mesh_shader_program_attributes.begin() + lower_index, + m_mesh_shader_program_attributes.begin() + upper_index + ); + + const auto upper_attribute = std::upper_bound( + relevant_attributes.begin(), relevant_attributes.end(), + attributes, + [](const auto& attributes, const auto& entry) { + return attributes < entry.attributes; + } + ); + + auto entry_it = std::prev(upper_attribute); + + if ( + upper_attribute != relevant_attributes.begin() and + entry_it->attributes != attributes + ) { + + const auto locations = attribute_location_flags(attributes, all_attributes); + + auto found_match = false; + + while (entry_it != relevant_attributes.begin()) + { + --entry_it; + + const auto& [ curr_attributes, curr_locations ] = *entry_it; + + // The candidate may not support additional attributes + if (curr_attributes & ~attributes) + { + continue; + } + + // The candidate may not be missing 'inner' attributes, + // as this creates location alignment problems. + const auto higher_neighbour_matched = (curr_locations & locations) >> 1; + const auto bubbles = higher_neighbour_matched & ~curr_locations; + + if (not bubbles) + { + found_match = true; + break; + } + } + + if (not found_match) + { + return std::nullopt; + } + } + + const auto shader_program_index = entry_it.base() - m_mesh_shader_program_attributes.begin().base(); + + return m_mesh_shader_programs[shader_program_index]; +} + +shader_program_lookup::attribute_locations_type shader_program_lookup::attribute_location_flags( + shader_program_handle::attribute_support_type attributes, + std::span all_attributes +) { + auto location_flags = ztu::u32{ 0 }; + + auto index = std::size_t{}; + + while (attributes) + { + if (attributes & 1) + { + const auto location = all_attributes[index].info.location; + location_flags |= attribute_locations_type{ 1 } << location; + } + + attributes >>= 1; + ++index; + } + + return location_flags; +} + +void shader_program_lookup::print() { + for (std::size_t i{}; i != m_mesh_shader_program_uniforms.size(); ++i) { + const auto shader = m_mesh_shader_programs[i]; + const auto uniforms = m_mesh_shader_program_uniforms[i]; + const auto [ attributes, locations ] = m_mesh_shader_program_attributes[i]; + ztu::logger::debug("[%] uniforms: % attributes: % locations: %", + shader.program_id, + std::bitset<32>(uniforms), + std::bitset<32>(attributes), + std::bitset<32>(locations) + ); + } +} +} diff --git a/source/rendering/batch_renderers/mesh_batch_renderer.cpp b/source/rendering/batch_renderers/mesh_batch_renderer.cpp new file mode 100644 index 0000000..aabedd1 --- /dev/null +++ b/source/rendering/batch_renderers/mesh_batch_renderer.cpp @@ -0,0 +1,341 @@ +#include "rendering/batch_renderers/mesh_batch_renderer.hpp" + +#include "shader_program/uniforms/mesh_uniforms.hpp" +#include "util/unroll_bool_template.hpp" +#include "util/logger.hpp" // TODO remove +#include // TODOE remove + +namespace rendering +{ + +mesh_batch_renderer::mesh_batch_renderer(int render_mode_count) + : m_render_mode_count{ render_mode_count } {}; + +std::pair mesh_batch_renderer::lookup_batch( + const batch_components_type& batch_components +) const { + + const auto component_it = std::upper_bound( + m_component_lookup.begin(), m_component_lookup.end(), + batch_components, + [](const auto& components, const auto& entry) + { + return components < entry.first; + } + ); + + const auto index = component_it - m_component_lookup.begin(); + + const auto match = ( + index != 0 and m_component_lookup[index - 1].first == batch_components + ); + + return { index - static_cast(match), match }; +} + +std::optional mesh_batch_renderer::add( + const batch_components_type& batch_component, + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const zgl::material_handle& material, + const shader_program_lookups::mesh_lookup& shader_program_lookup +) { + const auto [ lookup_index, lookup_match ] = lookup_batch(batch_component); + + std::size_t batch_index; + batch_id_type batch_id; + + if (lookup_match) + { + batch_index = m_component_lookup[lookup_index].second; + batch_id = m_id_lookup[batch_index]; + } + else + { + auto base_requirements = requirements::mesh::flags::position; + + const auto [ vertex_comps, material_comps ] = batch_component; + + // If no texture is provided, the uniform color is provided by ambient light. + if ((material_comps & material_component::flags::texture) == material_component::flags::none) + { + base_requirements |= requirements::mesh::flags::uniform_color; + } + + ztu::logger::debug("vertex_comps: %", std::bitset<32>{ static_cast(static_cast(vertex_comps)) }); + ztu::logger::debug("material_comps: %", std::bitset<32>{ static_cast(static_cast(material_comps)) }); + ztu::logger::debug("lit reqs: %", std::bitset<32>{ static_cast(static_cast(shader_program::capabilities::mesh::lit.uniforms)) }); + + + for (std::size_t i{}; i != requirements::mesh::all.size(); ++i) + { + const auto& requirement = requirements::mesh::all[i]; + + if ( + ( + requirement.vertex_requirements != components::mesh_vertex::flags::none and + (vertex_comps & requirement.vertex_requirements) == requirement.vertex_requirements + ) + and + ( + requirement.material_requirements != material_component::flags::none and + (material_comps & requirement.material_requirements) == requirement.material_requirements + ) + ) { + base_requirements |= requirements::mesh::flags{ 1 << i }; + } + } + + ztu::logger::debug("base reqs: %", std::bitset<32>{ static_cast(static_cast(base_requirements)) }); + + + const auto base_shader = shader_program_lookup.find(base_requirements); + if (not base_shader) + { + ztu::logger::warn("Could not find base shader!"); + return std::nullopt; + } + + const auto point_shader = shader_program_lookup.find(base_requirements | requirements::mesh::flags::point); + if (not point_shader) + { + ztu::logger::warn("Could not find point shader!"); + return std::nullopt; + } + + const auto lit_shader = shader_program_lookup.find(base_requirements | requirements::mesh::flags::lit); + if (not lit_shader) + { + ztu::logger::warn("Could not find lit shader!"); + return std::nullopt; + } + + auto shader_programs = std::array{ + *base_shader, + *point_shader, + *base_shader, + *lit_shader + }; + + ztu::logger::debug( + "shaders: % % %", + base_shader->program_id, + point_shader->program_id, + lit_shader->program_id + ); + + batch_index = m_batches.size(); + batch_id = m_next_batch_id++; + m_batches.emplace_back(batch_type{}, batch_component); + m_id_lookup.push_back(batch_id); + m_component_lookup.emplace(m_component_lookup.begin() + lookup_index, batch_component, batch_index); + m_shader_programs.insert( + m_shader_programs.begin() + lookup_index, + shader_programs.begin(), shader_programs.end() + ); + } + + auto& batch = m_batches[batch_index].first; + + const auto mesh_id = batch.add(mesh, bounding_box, transform, material); + + return id_type{ batch_id, mesh_id }; +} + +std::optional mesh_batch_renderer::bounding_box(id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id.first); + + if (lookup_it == m_id_lookup.end()) + { + return std::nullopt; + } + + const auto batch_index = lookup_it - m_id_lookup.begin(); + auto& batch = m_batches[batch_index].first; + + return batch.bounding_box(id.second); +} + +bool mesh_batch_renderer::remove(const id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id.first); + + if (lookup_it == m_id_lookup.end()) + { + return false; + } + + const auto batch_index = lookup_it - m_id_lookup.begin(); + auto& batch = m_batches[batch_index].first; + + // If batches can be removed the indices in m_component_lookup need to be changed. + return batch.remove(id.second); +} + + + +template +void render_mesh_batch( + const zgl::shader_program_handle& shader_program, + const mesh_batch& batch, + const glm::mat4& vp_matrix, + const glm::mat4& view_matrix, + const GLenum draw_mode +) { + const auto meshes = batch.meshes(); + const auto transforms = batch.transforms(); + const auto textures = batch.textures(); + const auto surface_properties = batch.surface_properties(); + const auto alphas = batch.alphas(); + + /*ztu::logger::debug("meshes: %", meshes.size()); + ztu::logger::debug("transforms: %", transforms.size()); + ztu::logger::debug("textures: %", textures.size()); + ztu::logger::debug("surface_properties: %", surface_properties.size()); + ztu::logger::debug("alphas: %", alphas.size()); + + ztu::logger::debug("textured: % alpha: % lit: %", Textured, Alpha, Lit);*/ + + namespace uniforms = shader_program::uniforms::mesh; + + for (std::size_t i{}; i != meshes.size(); ++i) + { + //ztu::logger::debug("Mesh: %", i); + + const auto& mesh = meshes[i]; + const auto& model_matrix = transforms[i]; + + const auto mvp_matrix = vp_matrix * model_matrix; + + shader_program.set_uniform(mvp_matrix); + + if constexpr (Textured) + { + textures[i].bind(); + } + + if constexpr (Lit) + { + shader_program.set_uniform(model_matrix); + + // TODO more efficient set + const auto& properties = surface_properties[i]; + shader_program.set_uniform(properties.ambient_filter); + shader_program.set_uniform(properties.diffuse_filter); + shader_program.set_uniform(properties.specular_filter); + shader_program.set_uniform(properties.shininess); + } + + if constexpr (Alpha) + { + + shader_program.set_uniform(alphas[i]); + } + + //ztu::logger::debug("vao: % valid: %%", mesh.vao_id, std::boolalpha, (bool)glIsVertexArray(mesh.vao_id)); + + mesh.bind(); + + //ztu::logger::debug("glDrawElements(%, %)", draw_mode, mesh.index_count); + + glDrawElements(draw_mode, mesh.index_count, GL_UNSIGNED_INT, nullptr); + + //ztu::logger::debug("done"); + } +} + +void mesh_batch_renderer::render( + const modes::mesh render_mode, + const glm::mat4& vp_matrix, + const glm::mat4& view_matrix, + const glm::vec3& view_pos, + const lighting_setup& lights +) { + + namespace uniforms = shader_program::uniforms::mesh; + + const auto render_mode_index = static_cast(render_mode); + + const auto lines = render_mode == modes::mesh::wire_frame; + const auto points = render_mode == modes::mesh::points; + const auto lit = render_mode == modes::mesh::lit_faces; + + for (std::size_t i{}; i != m_batches.size(); ++i) { + + //ztu::logger::debug("batch: %", i); + + const auto& [ batch, batch_components ] = m_batches[i]; + + const auto [ vertex_components, material_components ] = batch_components; + + const auto textured = ( + (vertex_components & components::mesh_vertex::flags::tex_coord) != components::mesh_vertex::flags::none + and (material_components & material_component::flags::texture) != material_component::flags::none + ); + + const auto alpha = ( + (material_components & material_component::flags::transparency) != material_component::flags::none + ); + + const auto draw_mode = points ? GLenum{ GL_POINTS } : GLenum{ GL_TRIANGLES }; + + const auto& shader_program = m_shader_programs[i * m_render_mode_count + render_mode_index]; + + //ztu::logger::debug("shader_program: % valid: %%", shader_program.program_id, std::boolalpha, (bool)glIsProgram(shader_program.program_id)); + + shader_program.bind(); + + if (lit) + { + // TODO set more efficiently + shader_program.set_uniform(view_pos); + shader_program.set_uniform(lights.point_light_direction); + shader_program.set_uniform(lights.point_light_color); + shader_program.set_uniform(lights.ambient_light_color); + } + + if (textured) + { + constexpr auto texture_unit = 0; + glActiveTexture(GL_TEXTURE0 + texture_unit); + shader_program.set_uniform(texture_unit); + } + else + { + shader_program.set_uniform(glm::vec4(lights.ambient_light_color, 1.0f)); + } + + if (lines) + { + glPolygonMode(GL_FRONT, GL_LINE); + glPolygonMode(GL_BACK, GL_LINE); + } + + unroll_bool_function_template( + [&]() { + render_mesh_batch( + shader_program, + batch, + vp_matrix, + view_matrix, + draw_mode + ); + }, + textured, lit, alpha + ); + + if (lines) + { + glPolygonMode(GL_FRONT, GL_FILL); + glPolygonMode(GL_BACK, GL_FILL); + } + } + + zgl::texture_handle::unbind(); + zgl::mesh_handle::unbind(); + zgl::shader_program_handle::unbind(); +} + +} diff --git a/source/rendering/batch_renderers/point_cloud_batch_renderer.cpp b/source/rendering/batch_renderers/point_cloud_batch_renderer.cpp new file mode 100644 index 0000000..f5b4c6f --- /dev/null +++ b/source/rendering/batch_renderers/point_cloud_batch_renderer.cpp @@ -0,0 +1,243 @@ +#include "rendering/batch_renderers/point_cloud_batch_renderer.hpp" + +#include + +#include "rendering/requirements/point_cloud_requirements.hpp" +#include "shader_program/uniforms/point_cloud_uniforms.hpp" +#include "util/unroll_bool_template.hpp" + +namespace rendering +{ + +point_cloud_batch_renderer::point_cloud_batch_renderer(int render_mode_count) + : m_render_mode_count{ render_mode_count } {}; + +std::pair point_cloud_batch_renderer::lookup_batch( + const batch_components_type& batch_component +) const { + const auto component_it = std::upper_bound( + m_component_lookup.begin(), m_component_lookup.end(), + batch_component, + [](const auto& batch_component, const auto& entry) + { + return batch_component < entry.first; + } + ); + + const auto index = component_it - m_component_lookup.begin(); + + const auto match = ( + index == 0 or m_component_lookup[index - 1].first == batch_component + ); + + return { index - static_cast(match), match }; +} + +std::optional point_cloud_batch_renderer::add( + batch_components_type batch_components, + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const shader_program_lookups::point_cloud_lookup& shader_program_lookup +) { + const auto [ lookup_index, lookup_match ] = lookup_batch(batch_components); + + std::size_t batch_index; + batch_id_type batch_id; + + if (lookup_match) { + batch_index = m_component_lookup[lookup_index].second; + batch_id = m_id_lookup[batch_index]; + } + else + { + auto base_requirements = requirements::point_cloud::flags{}; + + const auto vertex_comps = batch_components; + + for (std::size_t i{}; i != requirements::point_cloud::all.size(); ++i) + { + const auto& requirement = requirements::point_cloud::all[i]; + + if ( + (vertex_comps & requirement.vertex_requirements) != components::point_cloud_vertex::flags::none + ) { + base_requirements |= requirements::point_cloud::flags{ 1 << i }; + } + } + + const auto uniform_color_shader = shader_program_lookup.find( + base_requirements | requirements::point_cloud::flags::uniform_color + ); + + if (not uniform_color_shader) + { + return std::nullopt; + } + + const auto rainbow_shader = shader_program_lookup.find( + base_requirements | requirements::point_cloud::flags::rainbow + ); + + if (not rainbow_shader) + { + return std::nullopt; + } + + auto shader_programs = std::array{ + *uniform_color_shader, + *rainbow_shader + }; + + batch_index = m_batches.size(); + batch_id = m_next_batch_id++; + m_batches.emplace_back(batch_type{}, batch_components); + m_id_lookup.push_back(batch_id); + m_component_lookup.emplace(m_component_lookup.begin() + lookup_index, batch_components, batch_index); + m_shader_programs.insert( + m_shader_programs.begin() + lookup_index, + shader_programs.begin(), shader_programs.end() + ); + } + + auto& batch = m_batches[batch_index].first; + + const auto mesh_id = batch.add(point_cloud, bounding_box, transform); + + return id_type{ batch_id, mesh_id }; +} + +std::optional point_cloud_batch_renderer::bounding_box(id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id.first); + + if (lookup_it == m_id_lookup.end()) + { + return std::nullopt; + } + + const auto batch_index = lookup_it - m_id_lookup.begin(); + auto& batch = m_batches[batch_index].first; + + return batch.bounding_box(id.second); +} + +bool point_cloud_batch_renderer::remove(const id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id.first); + + if (lookup_it == m_id_lookup.end()) + { + return false; + } + + const auto batch_index = lookup_it - m_id_lookup.begin(); + auto& batch = m_batches[batch_index].first; + + // If batches can be removed the indices in m_component_lookup need to be changed. + return batch.remove(id.second); +} + + +template +void render_point_cloud_batch( + const zgl::shader_program_handle& shader_program, + const point_cloud_batch& batch, + const glm::mat4& vp_matrix, + const glm::vec3& camera_position +) { + const auto point_clouds = batch.point_clouds(); + const auto transforms = batch.transforms(); + + namespace uniforms = shader_program::uniforms::point_cloud; + + for (std::size_t i{}; i != point_clouds.size(); ++i) + { + const auto& point_cloud = point_clouds[i]; + const auto& model_matrix = transforms[i]; + + // TODO check order + const auto mvp_matrix = vp_matrix * model_matrix; + + shader_program.set_uniform(mvp_matrix); + + if constexpr (Normals) + { + shader_program.set_uniform(model_matrix); + shader_program.set_uniform(camera_position); + } + + point_cloud.bind(); + + using block_index_type = ztu::u16; + static constexpr auto block_size = static_cast( + std::numeric_limits::max() + ); + + for (GLsizei j{}; j < point_cloud.point_count; j += block_size) + { + const auto points_left = static_cast(point_cloud.point_count) - j; + const auto points_in_block = std::min(points_left, block_size); + glDrawArrays( + GL_POINTS, + j, + static_cast(points_in_block) + ); + } + } +} + +void point_cloud_batch_renderer::render( + const modes::point_cloud render_mode, + const glm::mat4& vp_matrix, + const glm::vec3& camera_position, + const lighting_setup& +) { + + namespace uniforms = shader_program::uniforms::point_cloud; + + const auto render_mode_index = static_cast(render_mode); + + glEnable(GL_PROGRAM_POINT_SIZE); + glEnable(GL_POINT_SMOOTH); + + const auto rainbow = render_mode == modes::point_cloud::rainbow; + + + for (std::size_t i{}; i != m_batches.size(); ++i) { + + const auto& [ batch, vertex_components ] = m_batches[i]; + + const auto normals = static_cast(vertex_components & components::point_cloud_vertex::flags::normal); + + const auto& shader_program = m_shader_programs[i * m_render_mode_count + render_mode_index]; + + shader_program.bind(); + + if (rainbow) + { + shader_program.set_uniform(0.0f); // TODO fix + shader_program.set_uniform(0.0f); // TODO fix + } + + unroll_bool_function_template( + [&]() { + render_point_cloud_batch( + shader_program, + batch, + vp_matrix, + camera_position + ); + }, + normals + ); + } + + glDisable(GL_PROGRAM_POINT_SIZE); + glDisable(GL_POINT_SMOOTH); + + zgl::point_cloud_handle::unbind(); + zgl::shader_program_handle::unbind(); +} + +} diff --git a/source/rendering/batches/mesh_batch.ipp b/source/rendering/batches/mesh_batch.ipp new file mode 100644 index 0000000..e7e0d68 --- /dev/null +++ b/source/rendering/batches/mesh_batch.ipp @@ -0,0 +1,147 @@ +#ifndef INCLUDE_MESH_BATCH_IMPLEMENTATION +# error Never include this file directly include 'mesh_batch.hpp' +#endif + +#include + + +inline mesh_batch::id_type mesh_batch::add( + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform, + const zgl::material_handle& material +) { + std::size_t index; + if (material.texture) + { + // Sort by texture id if possible, so meshes the same texture are rendered consecutively. + const auto texture_it = std::ranges::upper_bound( + m_textures, + *material.texture, + [](const auto& lhs, const auto& rhs) + { + return lhs.texture_id < rhs.texture_id; + } + ); + index = texture_it - m_textures.begin(); + } + else + { + // TODO inserting by vao might split up texture sequence, this needs more attention + // Otherwise, sort by vao, so meshes with the same vertices are rendered consecutively. + const auto mesh_it = std::ranges::upper_bound( + m_meshes, + mesh, + [](const auto& lhs, const auto& rhs) + { + return lhs.vao_id < rhs.vao_id; + } + ); + index = mesh_it - m_meshes.begin(); + } + + m_meshes.insert(m_meshes.begin() + index, mesh); + m_bounding_boxes.insert(m_bounding_boxes.begin() + index, bounding_box); + m_transforms.insert(m_transforms.begin() + index, transform); + + if (material.texture) + { + m_textures.insert(m_textures.begin() + index, *material.texture); + } + + if (material.surface_properties) + { + m_surface_properties.insert(m_surface_properties.begin() + index, *material.surface_properties); + } + + if (material.alpha) + { + m_alphas.insert(m_alphas.begin() + index, *material.alpha); + } + + const auto mesh_id = m_next_mesh_id++; + + m_id_lookup.insert(m_id_lookup.begin() + index, mesh_id); + + return mesh_id; +} + +std::optional mesh_batch::bounding_box(id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id); + + if (lookup_it == m_id_lookup.end()) + { + return std::nullopt; + } + + const auto index = lookup_it - m_id_lookup.begin(); + + const auto& base_bounding_box = m_bounding_boxes[index]; + const auto& transform = m_transforms[index]; + + return base_bounding_box.transformed(transform); +} + +inline bool mesh_batch::remove(const id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id); + + if (lookup_it == m_id_lookup.end()) { + return false; + } + + const auto index = lookup_it - m_id_lookup.begin(); + + m_id_lookup.erase(m_id_lookup.begin() + index); + m_meshes.erase(m_meshes.begin() + index); + m_bounding_boxes.erase(m_bounding_boxes.begin() + index); + m_transforms.erase(m_transforms.begin() + index); + + if (not m_textures.empty()) + { + m_textures.erase(m_textures.begin() + index); + } + + if (not m_surface_properties.empty()) + { + m_surface_properties.erase(m_surface_properties.begin() + index); + } + + if (not m_alphas.empty()) + { + m_alphas.erase(m_alphas.begin() + index); + } + + return true; +} + +inline std::span mesh_batch::meshes() const +{ + return m_meshes; +} + +inline std::span mesh_batch::bounding_boxes() const +{ + return m_bounding_boxes; +} + +inline std::span mesh_batch::transforms() const +{ + return m_transforms; +} + +inline std::span mesh_batch::textures() const +{ + return m_textures; +} + +inline std::span mesh_batch::surface_properties() const +{ + return m_surface_properties; +} + +inline std::span mesh_batch::alphas() const +{ + return m_alphas; +} diff --git a/source/rendering/batches/point_cloud_batch.ipp b/source/rendering/batches/point_cloud_batch.ipp new file mode 100644 index 0000000..20f5bf3 --- /dev/null +++ b/source/rendering/batches/point_cloud_batch.ipp @@ -0,0 +1,69 @@ +#ifndef INCLUDE_POINT_CLOUD_BATCH_IMPLEMENTATION +# error Never include this file directly include 'point_cloud_batch.hpp' +#endif + + +inline point_cloud_batch::id_type point_cloud_batch::add( + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + const zgl::model_matrix_handle& transform +) { + const auto new_id = m_next_id++; + + m_point_clouds.push_back(point_cloud); + m_transforms.push_back(transform); + m_bounding_boxes.push_back(bounding_box); + m_id_lookup.push_back(new_id); + + return new_id; +} + +std::optional point_cloud_batch::bounding_box(id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id); + + if (lookup_it == m_id_lookup.end()) + { + return std::nullopt; + } + + const auto index = lookup_it - m_id_lookup.begin(); + + const auto& base_bounding_box = m_bounding_boxes[index]; + const auto& transform = m_transforms[index]; + + return base_bounding_box.transformed(transform); +} + +inline bool point_cloud_batch::remove(id_type id) +{ + const auto lookup_it = std::ranges::find(m_id_lookup, id); + + if (lookup_it == m_id_lookup.end()) + { + return false; + } + + const auto index = lookup_it - m_id_lookup.begin(); + + m_id_lookup.erase(m_id_lookup.begin() + index); + m_point_clouds.erase(m_point_clouds.begin() + index); + m_transforms.erase(m_transforms.begin() + index); + + return true; +} + +inline std::span point_cloud_batch::point_clouds() const +{ + return m_point_clouds; +} + +inline std::span point_cloud_batch::transforms() const +{ + return m_transforms; +} + +inline std::span point_cloud_batch::bounding_boxes() const +{ + return m_bounding_boxes; +} diff --git a/source/rendering/shader_program_lookups/mesh_lookup.cpp b/source/rendering/shader_program_lookups/mesh_lookup.cpp new file mode 100644 index 0000000..5566db7 --- /dev/null +++ b/source/rendering/shader_program_lookups/mesh_lookup.cpp @@ -0,0 +1,58 @@ +#include "rendering/shader_program_lookups/mesh_lookup.hpp" + +#include "util/logger.hpp" // TODO remove +#include // TODO remove + +namespace rendering::shader_program_lookups +{ + +void mesh_lookup::add( + const zgl::shader_program_handle& shader_program_handle +) { + m_shader_program_lookup.add( + shader_program_handle, + shader_program::attributes::mesh::all, + shader_program::uniforms::mesh::all + ); +} + +std::optional mesh_lookup::find( + requirements::mesh::flags requirements +) const { + auto capability = shader_program::capabilities::mesh::type{}; + + auto index = std::size_t{}; + + auto requirement_flags = static_cast(requirements); + + while (requirement_flags) + { + if (requirement_flags & 1) + { + const auto shader_requirements_index = requirements::mesh::all[index].shader_program_requirement_index; + const auto& [ attributes, uniforms ] = shader_program::capabilities::mesh::all[shader_requirements_index]; + capability.attributes |= attributes; + capability.uniforms |= uniforms; + } + + requirement_flags >>= 1; + ++index; + } + + // TODO if not textured and not colored add ucolor "for free" + + ztu::logger::debug("attributes reqs: %", std::bitset<32>{ static_cast(static_cast(capability.attributes)) }); + ztu::logger::debug("uniforms reqs: %", std::bitset<32>{ static_cast(static_cast(capability.uniforms)) }); + + return m_shader_program_lookup.find( + static_cast(capability.attributes), + static_cast(capability.uniforms), + shader_program::attributes::mesh::all + ); +} + +void mesh_lookup::print() { + m_shader_program_lookup.print(); +} + +} diff --git a/source/rendering/shader_program_lookups/point_cloud_lookup.cpp b/source/rendering/shader_program_lookups/point_cloud_lookup.cpp new file mode 100644 index 0000000..d5ebff4 --- /dev/null +++ b/source/rendering/shader_program_lookups/point_cloud_lookup.cpp @@ -0,0 +1,47 @@ +#include "rendering/shader_program_lookups/point_cloud_lookup.hpp" + +namespace rendering::shader_program_lookups +{ + +void point_cloud_lookup::add( + const zgl::shader_program_handle& shader_program_handle +) { + m_program_lookup.add( + shader_program_handle, + shader_program::attributes::point_cloud::all, + shader_program::uniforms::point_cloud::all + ); +} + + +std::optional point_cloud_lookup::find( + requirements::point_cloud::flags requirements +) const { + auto capability = shader_program::capabilities::point_cloud::type{}; + + auto index = std::size_t{}; + + auto requirement_flags = static_cast(requirements); + + while (requirement_flags) + { + if (requirement_flags & 1) + { + const auto shader_requirements_index = requirements::point_cloud::all[index].shader_program_requirement_index; + const auto& [ attributes, uniforms ] = shader_program::capabilities::point_cloud::all[shader_requirements_index]; + capability.attributes |= attributes; + capability.uniforms |= uniforms; + } + + requirement_flags >>= 1; + ++index; + } + + return m_program_lookup.find( + static_cast(capability.attributes), + static_cast(capability.uniforms), + shader_program::attributes::point_cloud::all + ); +} + +} diff --git a/source/scene/flying_camera.cpp b/source/scene/flying_camera.cpp new file mode 100755 index 0000000..79ec883 --- /dev/null +++ b/source/scene/flying_camera.cpp @@ -0,0 +1,117 @@ +#include "scene/flying_camera.hpp" + +#include +#include +#include +#include "glm/gtx/string_cast.hpp" +#include "glm/gtx/euler_angles.hpp" + +#include "util/logger.hpp" // TODO remove +#include "glm/gtx/vector_angle.hpp" + +flying_camera::flying_camera(const float yaw, const float pitch, const float roll) { + m_world_rotation = glm::mat3(glm::eulerAngleYXZ(yaw, pitch, roll)); + m_world_up = m_world_rotation * glm::vec3(0, 1, 0); + + m_velocity = glm::vec3(0.f, 0.f, 0.f); + m_pitch = 0.f; + m_yaw = glm::radians(-90.f); + m_roll = 0.f; +} + + +void flying_camera::update( + const float time_delta, + const glm::vec2 mouse_pos_delta, + const float mouse_wheel_delta, + camera_view& view +) { + static constexpr auto pi = std::numbers::pi_v; + static constexpr auto epsilon = std::numeric_limits::epsilon(); + static constexpr auto world_up = glm::vec3(0.0f, 1.0f, 0.0f); + + static constexpr auto friction_coefficient = 25.0f; + static constexpr auto walk_acceleration = 3000.0f; + static constexpr auto max_velocity = 4000.0f; + + static constexpr float max_pitch = (pi / 2.0f) - epsilon; + static constexpr float min_fov = 0.01f * pi; + static constexpr float max_fov = 0.8f * pi; + + m_yaw += mouse_pos_delta.x; + m_pitch -= mouse_pos_delta.y; + + m_yaw = std::fmod(m_yaw, 2.0f * pi); + m_pitch = std::clamp(m_pitch, -max_pitch, max_pitch); + + view.front.x = std::cos(m_yaw) * std::cos(m_pitch); + view.front.y = std::sin(m_pitch); + view.front.z = std::sin(m_yaw) * std::cos(m_pitch); + view.front = glm::normalize(view.front); + + view.right = glm::normalize(glm::cross(view.front, world_up)); + view.up = glm::normalize(glm::cross(view.right, view.front)); + + view.front = m_world_rotation * view.front; + view.up = m_world_rotation * view.up; + view.right = m_world_rotation * view.right; + + view.fov *= 1.0f + mouse_wheel_delta; + view.fov = std::clamp(view.fov, min_fov, max_fov); + + using kb = sf::Keyboard; + auto acceleration = glm::vec3{ 0.0f }; + + if (kb::isKeyPressed(kb::W)) acceleration += view.front; + if (kb::isKeyPressed(kb::S)) acceleration -= view.front; + + if (kb::isKeyPressed(kb::A)) acceleration -= view.right; + if (kb::isKeyPressed(kb::D)) acceleration += view.right; + + if (kb::isKeyPressed(kb::Space)) acceleration += m_world_up; // TODO fix + if (kb::isKeyPressed(kb::LControl)) acceleration -= m_world_up; + + acceleration *= walk_acceleration; + + const auto acc = glm::length(acceleration); + if (acc > epsilon) + { + acceleration *= walk_acceleration / acc; + } + + if (kb::isKeyPressed(kb::LShift)) acceleration *= 2.0f; + + m_velocity += acceleration * time_delta; + + const float drag = std::exp(-friction_coefficient * time_delta); + m_velocity *= drag; + + + const auto speed = time_delta * glm::length(m_velocity); + if (speed > max_velocity) { + m_velocity *= (max_velocity / speed) * max_velocity; + } + + view.position += m_velocity * time_delta; +} + +void flying_camera::look_at( + const glm::vec3& origin, + const glm::vec3& target, + camera_view& view +) { + static constexpr auto world_up = glm::vec3(0.0f, 1.0f, 0.0f); + // TODO inverted matrix + + view.position = origin; + view.front = glm::normalize(target - origin); + view.right = glm::normalize(glm::cross(view.front, world_up)); + view.up = glm::normalize(glm::cross(view.right, view.front)); + view.fov = std::numbers::pi_v / 2.0f; + + m_velocity = { 0.0f, 0.0f, 0.0f }; + + m_pitch = std::asin(glm::dot(view.front, world_up)); + m_yaw = std::atan2(view.front.z, view.front.x); + m_roll = std::atan2(view.up.x, view.up.y); +} diff --git a/source/viewer/asset_loader.cpp b/source/viewer/asset_loader.cpp new file mode 100644 index 0000000..0f0bfc9 --- /dev/null +++ b/source/viewer/asset_loader.cpp @@ -0,0 +1,636 @@ +#include "viewer/asset_loader.hpp" + +#include + +#include "geometry/aabb.hpp" +#include "util/logger.hpp" +#include "glm/gtx/string_cast.hpp" // TODO remove + +namespace viewer +{ +std::error_code asset_loader::init( + components::mesh_vertex::flags enabled_mesh_components, + material_component::flags enabled_material_components, + components::point_cloud_vertex::flags enabled_point_cloud_components, + const dynamic_material_data& default_material +) { + + //m_ctx.setActive(true); + + m_enabled_mesh_components = enabled_mesh_components; + m_enabled_material_components = enabled_material_components; + m_enabled_point_cloud_components = enabled_point_cloud_components; + + m_dynamic_material_data_buffer.push_back(default_material); + + return create_gl_materials(); +} + +std::error_code asset_loader::load_shader( + const GLenum type, + const std::filesystem::path& filename, + zgl::shader_handle& shader_handle +) { + + auto& [ buffer_source, buffer_type ] = m_dynamic_shader_data_buffer; + + std::stringstream source_stream; + static constexpr auto glsl_version = 460; + + source_stream << "#version " << glsl_version << '\n'; + source_stream << "#define " << "hello" << '\n'; + source_stream << buffer_source; + + buffer_type = type; + + if (filename.empty()) + { + buffer_source = ""; + } + else + { + if (const auto e = m_shader_loader.load(filename, buffer_source)) { + ztu::logger::warn( + "Could not load shader_program_data source file %: [%] %", + filename, + e.category().name(), + e.message() + ); + } + } + + if (const auto e = create_gl_shader()) { + return e; + } + + shader_handle = m_gl_shader_data.back().handle(); + + return {}; +} + +std::error_code asset_loader::build_shader_program( + const zgl::shader_handle& vertex_shader, + const zgl::shader_handle& geometry_shader, + const zgl::shader_handle& fragment_shader, + zgl::shader_program_handle& shader_program_handle +) { + + auto program_data = zgl::shader_program_data{}; + + if (const auto e = zgl::shader_program_data::build_from( + vertex_shader, + geometry_shader, + fragment_shader, + program_data + )) { + return e; + } + + shader_program_handle = program_data.handle(); + + m_gl_shader_program_data.emplace_back(std::move(program_data)); + + return {}; +} + +std::error_code asset_loader::load_asset( + const std::string& format, + const std::filesystem::path& filename, + std::vector>& dynamic_mesh_handles, + std::vector& dynamic_point_cloud_handles +) { + std::error_code error; + + if ((error = load_mesh( + format, filename, dynamic_mesh_handles + ))) { + if ( + error.category() == std::generic_category() and + static_cast(error.value()) == std::errc::invalid_argument + ) { + error = load_point_cloud(format, filename, dynamic_point_cloud_handles); + } + } + + return error; +} + +std::error_code asset_loader::load_mesh( + const std::string& format, + const std::filesystem::path& filename, + std::vector>& dynamic_mesh_handles +) { + const auto mesh_loader_id = m_mesh_loader.find_loader(format); + + if (not mesh_loader_id) + { + return std::make_error_code(std::errc::invalid_argument); + } + + if (const auto e = m_mesh_loader.read( + *mesh_loader_id, + filename, + m_dynamic_mesh_data_buffer, + m_enabled_mesh_components, + m_dynamic_material_data_buffer, + m_enabled_material_components, + next_materials_id, + true + )) { + return e; + } + + return process_materials_and_meshes(dynamic_mesh_handles); +} + +std::error_code asset_loader::load_point_cloud( + const std::string& format, + const std::filesystem::path& filename, + std::vector& dynamic_point_cloud_handles +) { + const auto point_cloud_loader_id = m_point_cloud_loader.find_loader(format); + + if (not point_cloud_loader_id) + { + return std::make_error_code(std::errc::invalid_argument); + } + + if (const auto e = m_point_cloud_loader.read( + *point_cloud_loader_id, + filename, + m_dynamic_point_cloud_buffer, + true + )) { + return e; + } + + return process_point_clouds(dynamic_point_cloud_handles); +} + +std::error_code asset_loader::load_asset_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector>& dynamic_mesh_handles, + std::vector& dynamic_point_cloud_handles +) { + std::error_code error; + + if ((error = load_mesh_directory( + format, path, dynamic_mesh_handles + ))) { + if ( + error.category() == std::generic_category() and + static_cast(error.value()) == std::errc::invalid_argument + ) { + error = load_point_cloud_directory(format, path, dynamic_point_cloud_handles); + } + } + + return error; +} +std::error_code asset_loader::load_mesh_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector>& dynamic_mesh_handles +) { + const auto mesh_loader_id = m_mesh_loader.find_loader(format); + + if (not mesh_loader_id) + { + return std::make_error_code(std::errc::invalid_argument); + } + + if (const auto e = m_mesh_loader.read_directory( + *mesh_loader_id, + path, + m_dynamic_mesh_data_buffer, + m_enabled_mesh_components, + m_dynamic_material_data_buffer, + m_enabled_material_components, + next_materials_id, + true + )) { + return e; + } + + return process_materials_and_meshes(dynamic_mesh_handles); +} + + +std::error_code asset_loader::load_point_cloud_directory( + const std::string& format, + const std::filesystem::path& path, + std::vector& dynamic_point_cloud_handles +) { + const auto point_cloud_loader_id = m_point_cloud_loader.find_loader(format); + + if (not point_cloud_loader_id) + { + return std::make_error_code(std::errc::invalid_argument); + } + + if (const auto e = m_point_cloud_loader.read_directory( + *point_cloud_loader_id, + path, + m_dynamic_point_cloud_buffer, + true + )) { + return e; + } + + return process_point_clouds(dynamic_point_cloud_handles); +} + +std::error_code asset_loader::process_materials_and_meshes( + std::vector>& dynamic_mesh_handles +) { + + const auto material_count_before = m_gl_material_data_references.size(); + + if (const auto e = create_gl_materials()) + { + m_dynamic_mesh_data_buffer.clear(); + return e; + } + + const auto new_materials = std::span( + m_gl_material_data_references.begin() + material_count_before, + m_gl_material_data_references.end() + ); + + const auto mesh_count_before = m_gl_mesh_data.size(); + + create_gl_meshes(new_materials); + + const auto new_meshes = std::span( + m_gl_mesh_data.begin() + mesh_count_before, + m_gl_mesh_data.end() + ); + + const auto dynamic_mesh_count_before = dynamic_mesh_handles.size(); + dynamic_mesh_handles.resize(dynamic_mesh_handles.size() + new_meshes.size()); + + std::ranges::transform( + new_meshes, + dynamic_mesh_handles.begin() + dynamic_mesh_count_before, + [&](const auto& entry) + { + const auto& [ gl_mesh_data, bounding_box ] = entry; + const auto material_id = gl_mesh_data.material_id(); + + auto material_index = std::size_t{ 0 }; + + if (material_id != 0) + { + const auto material_reference_it = std::ranges::find_if( + new_materials, + [&material_id](const auto& entry) { + return entry.first == material_id; + } + ); + + if (material_reference_it == new_materials.end()) + { + ztu::logger::error( + "Something went horribly wrong while searching for material. Falling back to default material" + ); + } + else + { + material_index = material_reference_it.base() - m_gl_material_data_references.begin().base(); + } + } + + const auto& gl_material = m_gl_material_data[material_index]; + + //ztu::logger::debug("mesh components: %", static_cast(gl_mesh_data.components())); + //ztu::logger::debug("material components: %", static_cast(gl_material.components())); + + return std::make_pair( + dynamic_mesh_handle_type{ + .handle = gl_mesh_data.handle(), + .bounding_box = bounding_box, + .components = gl_mesh_data.components() + }, + dynamic_material_handle_type{ + .handle = gl_material.handle(), + .components = gl_material.components() + } + ); + } + ); + + return {}; +} + +std::error_code asset_loader::process_point_clouds( + std::vector& dynamic_point_cloud_handles +) { + const auto point_cloud_count_before = m_gl_point_cloud_data.size(); + + create_gl_point_clouds(); + + const auto new_point_clouds = std::span( + m_gl_point_cloud_data.begin() + point_cloud_count_before, + m_gl_point_cloud_data.end() + ); + + const auto dynamic_point_cloud_count_before = dynamic_point_cloud_handles.size(); + dynamic_point_cloud_handles.resize(dynamic_point_cloud_handles.size() + new_point_clouds.size()); + + std::ranges::transform( + new_point_clouds, + dynamic_point_cloud_handles.begin() + dynamic_point_cloud_count_before, + [&](const auto& gl_point_cloud_data) + { + const auto& [ data, box ] = gl_point_cloud_data; + return dynamic_point_cloud_handle_type{ + .handle = data.handle(), + .bounding_box = box, + .components = data.components() + }; + } + ); + + return {}; +} + +std::error_code asset_loader::create_gl_materials() +{ + auto error = std::error_code{}; + + for (const auto& material_data : m_dynamic_material_data_buffer) + { + auto gl_material_data = zgl::material_data{}; + + if ((error = zgl::material_data::build_from( + material_data.texture(), + material_data.surface_properties(), + material_data.transparency(), + material_data.components(), + gl_material_data + ))) { + ztu::logger::error( + "Error while creating material gpu handle: [%] %", + error.category().name(), + error.message() + ); + } + else + { + m_gl_material_data.emplace_back(std::move(gl_material_data)); + m_gl_material_data_references.emplace_back(next_materials_id, 0); + } + + ++next_materials_id; + } + + m_dynamic_material_data_buffer.clear(); + + return error; +} + + +void asset_loader::create_gl_meshes(std::span material_references) +{ + auto component_type_buffer = std::array(components::mesh_vertex::count)>{}; + auto component_length_buffer = std::array(components::mesh_vertex::count)>{}; + + auto component_stride = GLsizei{}; + auto component_count = ztu::u32{}; + + for (auto& mesh_data : m_dynamic_mesh_data_buffer) + { + if (mesh_data.triangles().empty()) + { + ztu::logger::warn("Skipping mesh with empty index buffer."); + continue; + } + + const auto material_id = mesh_data.material_id(); + + auto material_index = std::size_t{ 0 }; + + if (material_id != 0) // Default material is always there + { + const auto material_reference_it = std::ranges::find_if( + material_references, + [&material_id](const material_reference_entry_type& entry) { + return entry.first == material_id; + } + ); + + if (material_reference_it == material_references.end()) + { + ztu::logger::error("Skipping mesh because referenced material cannot be found"); + continue; + } + + material_index = material_reference_it - material_references.begin(); + } + + // Add normals if missing + if ((mesh_data.components() & components::mesh_vertex::flags::normal) == components::mesh_vertex::flags::none) + { + ztu::logger::warn("Model is missing normal vectors, so they are estimated!"); + estimate_normals( + mesh_data.positions(), + mesh_data.triangles(), + mesh_data.normals() + ); + mesh_data.components() |= components::mesh_vertex::flags::normal; + } + + auto mesh_box = aabb{}; + mesh_box.add_points(mesh_data.positions()); + + mesh_data.build_vertex_buffer( + m_vertex_buffer, + component_count, + component_type_buffer, + component_length_buffer, + component_stride + ); + + auto gl_mesh_data = zgl::mesh_data{}; + + const auto& first_triangle = mesh_data.triangles().front(); + + // TODO make span of size component_count + + if (const auto e = zgl::mesh_data::build_from( + m_vertex_buffer, + std::span(component_type_buffer).subspan(0, component_count), + std::span(component_length_buffer).subspan(0, component_count), + component_stride, + std::span( + first_triangle.data(), + mesh_data.triangles().size() * first_triangle.size() + ), + mesh_data.material_id(), + mesh_data.components(), + gl_mesh_data + )) { + ztu::logger::error( + "Error while creating opengl mesh data: [%] %\nMesh will be skipped.", + e.category().name(), + e.message() + ); + } + + ++m_gl_material_data_references[material_index].second; + + m_gl_mesh_data.emplace_back(std::move(gl_mesh_data), mesh_box); + } + + m_dynamic_mesh_data_buffer.clear(); +} + +void asset_loader::create_gl_point_clouds() +{ + auto component_type_buffer = std::array(components::point_cloud_vertex::count)>{}; + auto component_length_buffer = std::array(components::point_cloud_vertex::count)>{}; + auto component_stride = GLsizei{}; + auto component_count = ztu::u32{}; + + for (const auto& point_cloud_data : m_dynamic_point_cloud_buffer) + { + if (point_cloud_data.positions().empty()) + { + ztu::logger::warn("Skipping point cloud without points."); + continue; + } + + auto point_cloud_box = aabb{}; + point_cloud_box.add_points(point_cloud_data.positions()); + + point_cloud_data.build_vertex_buffer( + m_vertex_buffer, + component_count, + component_type_buffer, + component_length_buffer, + component_stride + ); + + auto gl_point_cloud_data = zgl::point_cloud_data{}; + + if (const auto e = zgl::point_cloud_data::build_from( + m_vertex_buffer, + component_type_buffer, + component_length_buffer, + component_stride, + gl_point_cloud_data + )) { + ztu::logger::error( + "Error while creating opengl point cloud data: [%] %\nPoint cloud will be skipped.", + e.category().name(), + e.message() + ); + } + + m_gl_point_cloud_data.emplace_back(std::move(gl_point_cloud_data), point_cloud_box); + } + + m_dynamic_point_cloud_buffer.clear(); +} + +std::error_code asset_loader::create_gl_shader() +{ + auto shader_data = zgl::shader_data{}; + + const auto& [source, type] = m_dynamic_shader_data_buffer; + + if (const auto e = zgl::shader_data::build_from(type, source, shader_data)) { + return e; + } + + m_gl_shader_data.emplace_back(std::move(shader_data)); + + + return {}; +} + + +bool asset_loader::unload(const zgl::shader_program_handle& shader_handle) +{ + const auto it = std::find_if( + m_gl_shader_program_data.begin(), m_gl_shader_program_data.end(), + [&shader_handle](const auto& gl_shader_data) { + return gl_shader_data.handle().program_id == shader_handle.program_id; + } + ); + + if (it == m_gl_shader_program_data.end()) + { + return false; + } + + m_gl_shader_program_data.erase(it); + + return true; +} + +bool asset_loader::unload(const zgl::mesh_handle& mesh_handle) +{ + const auto it = std::ranges::find_if( + m_gl_mesh_data, + [&mesh_handle](const auto& gl_mesh_data) { + return gl_mesh_data.first.handle().vao_id == mesh_handle.vao_id; + } + ); + + if (it == m_gl_mesh_data.end()) + { + return false; + } + + const auto material_id = it->first.material_id(); + + const auto reference_it = std::ranges::find_if( + m_gl_material_data_references, + [&material_id](const auto& entry) { + return entry.first == material_id; + } + ); + + if (reference_it != m_gl_material_data_references.end()) + { + // Do not delete default material at index 0 + if (--reference_it->second == 0 and reference_it != m_gl_material_data_references.begin()) + { + const auto index = reference_it - m_gl_material_data_references.begin(); + m_gl_material_data.erase(m_gl_material_data.begin() + index); + m_gl_material_data_references.erase(reference_it); + } + } + + m_gl_mesh_data.erase(it); + + return true; +} + +void asset_loader::unload_shader_data() +{ + m_gl_shader_data.clear(); +} + + +bool asset_loader::unload(const zgl::point_cloud_handle& point_cloud_handle) +{ + const auto it = std::ranges::find_if( + m_gl_point_cloud_data, + [&point_cloud_handle](const auto& entry) { + return entry.first.handle().vao_id == point_cloud_handle.vao_id; + } + ); + + if (it == m_gl_point_cloud_data.end()) + { + return false; + } + + m_gl_point_cloud_data.erase(it); + + return true; +} +} diff --git a/source/viewer/dynamic_shader_program_loading.cpp b/source/viewer/dynamic_shader_program_loading.cpp new file mode 100644 index 0000000..c686fd2 --- /dev/null +++ b/source/viewer/dynamic_shader_program_loading.cpp @@ -0,0 +1,275 @@ +#include "../../include/viewer/dynamic_shader_program_loading.hpp" +#include "../../include/util/string_lookup.hpp" +#include "../../include/util/logger.hpp" +#include + +std::size_t viewer::dynamic_shader_program_loading::count_shader_files( + const std::filesystem::path& path +) { + using namespace std::string_view_literals; + namespace fs = std::filesystem; + + auto shader_file_count = std::size_t{ 0 }; + + for (const auto& [ asset_type, folder ] : { + std::make_pair(asset_types::mesh, "mesh"sv), + std::make_pair(asset_types::point_cloud, "point_cloud"sv) + }) { + const auto folder_begin = fs::directory_iterator{ path / folder }; + const auto folder_end = fs::directory_iterator{}; + + shader_file_count += std::count_if( + folder_begin, folder_end, + [](auto& entry) { return entry.is_regular_file(); } + ); + } + + return shader_file_count; +} + +void viewer::dynamic_shader_program_loading::load_directory( + asset_loader& loader, + instance& z3d, + std::mutex& gl_resource_lock, + std::mutex& progress_lock, + std::string& progress_title, + float& progress_ratio, + const std::filesystem::path& path +) { + + namespace fs = std::filesystem; + using namespace std::string_view_literals; + + auto progress_builder = std::stringstream{}; + + const auto shader_file_count = static_cast(count_shader_files(path)); + constexpr auto shader_loading_progress = 0.8f; + + + auto shader_indices = ztu::string_lookup({ + { "vertex", 0 }, + { "geometry", 1 }, + { "fragment", 2 } + }); + + constexpr auto shader_types = std::array{ + GL_VERTEX_SHADER, + GL_GEOMETRY_SHADER, + GL_FRAGMENT_SHADER + }; + + auto program_capabilities = std::vector{}; + auto programs = std::vector>{}; + + auto capability_indices = ztu::string_lookup(); + auto capabilities = std::vector{}; + capabilities.reserve(8); + + constexpr auto dot_char = '.'; + constexpr auto separator_char = '_'; + constexpr auto optional_char = '?'; + + auto curr_shader_count = std::size_t{ 0 }; + + for (const auto& [ asset_type, folder ] : { + std::make_pair(asset_types::mesh, "mesh"sv), + std::make_pair(asset_types::point_cloud, "point_cloud"sv), + }) { + program_capabilities.clear(); + programs.clear(); + capability_indices.clear(); + + for (const auto& file : fs::directory_iterator{ path / folder }) + { + if (not file.is_regular_file()) + continue; + + const auto& file_path = file.path(); + + if (file_path.extension() != ".glsl") + continue; + + const auto filename = file_path.filename().string(); + + progress_lock.lock(); + + progress_builder.str(std::string{}); + progress_builder << "Loading shader '" << filename << "\'..."; + progress_title = progress_builder.str(); + progress_ratio = shader_loading_progress * static_cast(curr_shader_count) / shader_file_count; + + progress_lock.unlock(); + + const auto name = std::string_view(filename.begin(), std::ranges::find(filename, dot_char)); + + const auto type_str = std::string_view(name.begin(), std::ranges::find(name, separator_char)); + + const auto shader_type_index_it = shader_indices.find(type_str); + if (shader_type_index_it == shader_indices.end()) { + ztu::logger::warn("Unknown shader type '%'. Skipping shader.", type_str); + continue; + } + + const auto shader_type_index = shader_type_index_it->second; + const auto shader_type = shader_types[shader_type_index]; + + auto shader_handle = zgl::shader_handle{}; + if (const auto e = loader.load_shader(shader_type, file_path, shader_handle)) { + ztu::logger::error( + "Error while loading shader %: [%] %", + file_path, + e.category().name(), + e.message() + ); + continue; + } + + ztu::logger::debug("% -> %", filename, shader_handle.shader_id); + + capabilities.clear(); + capabilities.push_back(0); + + auto specifiers_str = std::string_view(type_str.end() + 1, name.end()); // skip separator_char + + while (not specifiers_str.empty()) + { + const auto pos = specifiers_str.find(separator_char); + auto specifier_str = specifiers_str.substr(0, pos); + + if (pos == std::string_view::npos) + { + specifiers_str = std::string_view{}; + } + else + { + specifiers_str = specifiers_str.substr(pos + 1); // skip separator_char + } + + if (specifier_str.empty()) + { + continue; + } + + const auto optional = specifier_str.back() == optional_char; + if (optional) + { + specifier_str = specifier_str.substr(0, specifier_str.size() - 1); + } + + const auto index_it = capability_indices.find(specifier_str); + + auto capability_index = capability_indices.size(); + if (index_it == capability_indices.end()) + { + capability_indices.emplace(specifier_str, capability_index); + } + else + { + capability_index = index_it->second; + } + + const auto capability_flag = ztu::u32{ 1 } << capability_index; + + const auto capability_count = capabilities.size(); + + if (optional) + { + capabilities.resize(2 * capability_count); + std::copy_n(capabilities.begin(), capability_count, capabilities.begin() + capability_count); + } + + for (std::size_t i{}; i != capability_count; ++i) + { + capabilities[i] |= capability_flag; + } + } + + for (const auto& capability : capabilities) + { + const auto program_capability_it = std::ranges::upper_bound(program_capabilities, capability); + + auto program_index = program_capability_it - program_capabilities.begin(); + + if ( + program_capability_it == program_capabilities.begin() or + *std::prev(program_capability_it) != capability + ) { + program_capabilities.insert(program_capability_it, capability); + programs.emplace(programs.begin() + program_index); + } + else + { + --program_index; // The element before the iterator matches. + } + + programs[program_index][shader_type_index] = shader_handle; + } + + ++curr_shader_count; + } + + progress_lock.lock(); + + progress_title = "Linking programs..."; + + progress_lock.unlock(); + + // Remove any duplicates shader combinations. + std::ranges::sort( + programs, + [](const auto& lhs, const auto& rhs) { + return std::lexicographical_compare( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + [](const auto& a, const auto& b) { + return a.shader_id < b.shader_id; + } + ); + } + ); + programs.erase(std::ranges::unique(programs).begin(), programs.end()); + + ztu::logger::debug("Linking % programs.", programs.size()); + + // create shader_program + for (const auto& [vertex, geometry, fragment] : programs) + { + if (vertex.shader_id == 0 or fragment.shader_id == 0) + { + ztu::logger::warn( + "Skipping program as the combination is unlikely to be used (vertex: % geometry: % fragment: %).", + vertex.shader_id, geometry.shader_id, fragment.shader_id + ); + continue; + } + + auto program_handle = zgl::shader_program_handle{}; + if (const auto e = loader.build_shader_program(vertex, geometry, fragment, program_handle)) + { + ztu::logger::error( + "Error occurred while linking shader program: [%] %", + e.category().name(), + e.message() + ); + continue; + } + + ztu::logger::debug( + "Linked (vertex: % geometry: % fragment: %) -> %", + vertex.shader_id, geometry.shader_id, fragment.shader_id, + program_handle.program_id + ); + + + gl_resource_lock.lock(); + z3d.add_shader_program(asset_type, program_handle); + gl_resource_lock.unlock(); + } + + gl_resource_lock.lock(); + z3d.m_mesh_shader_program_lookup.print(); + gl_resource_lock.unlock(); + + loader.unload_shader_data(); + } +} diff --git a/source/viewer/instance.cpp b/source/viewer/instance.cpp new file mode 100644 index 0000000..da81b45 --- /dev/null +++ b/source/viewer/instance.cpp @@ -0,0 +1,504 @@ +#include "viewer/instance.hpp" +#include "viewer/asset_loader.hpp" + +#include "SFML/Window.hpp" +#include "SFML/Graphics/Text.hpp" +#include "SFML/Graphics/Shape.hpp" +#include "SFML/Graphics/RectangleShape.hpp" +#include "SFML/Graphics/Sprite.hpp" + +#include +#include +#include "glm/gtx/euler_angles.hpp" + +namespace viewer +{ + +instance::instance() : + m_context_settings{ 24, 8, 2, 4, 6 }, + m_mesh_renderer(static_cast(rendering::modes::mesh::count)), + m_point_cloud_renderer(static_cast(rendering::modes::point_cloud::count)), + //m_camera(0.0f, 0.0f, 0.0f) + m_camera(0.0f, -std::numbers::pi_v / 2.0f, std::numbers::pi_v) +{ +} + +std::error_code instance::init(std::string title) +{ + m_title = std::move(title); + + windowed(512, 344, true); + + if (glewInit() != GLEW_OK) + { + return std::make_error_code(std::errc::not_supported); + } + + m_settings.lighting.point_light_direction = glm::normalize(m_settings.lighting.point_light_direction); + + m_mesh_render_mode = rendering::modes::mesh::lit_faces; + m_point_cloud_render_mode = rendering::modes::point_cloud::rainbow; + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glClearDepth(1.f); + + set_background_color({ 0.0f, 0.0f, 0.0f, 0.0f }); + + const auto current_path = std::filesystem::current_path(); + const auto data_path = current_path / ".." / "data"; + + const auto font_filename = data_path / "fonts" / "JetBrainsMono_Medium.ttf"; + if (not m_font.loadFromFile(font_filename)) { + ztu::logger::error("Could not open font file: %", font_filename); + } + + const auto image_dir = data_path / "images"; + + const auto logo_filename = image_dir/ "logo.png"; + if (not m_logo.loadFromFile(logo_filename)) { + ztu::logger::error("Could not open image file: %", logo_filename); + } + + const auto spinner_filename = image_dir/ "spinner.png"; + if (not m_spinner.loadFromFile(spinner_filename)) { + ztu::logger::error("Could not open image file: %", spinner_filename); + } + + return {}; +} + +void instance::set_background_color(const glm::vec4& color) +{ + glClearColor(color.r, color.g, color.b, color.a); +} + +std::optional instance::add_mesh( + const zgl::mesh_handle& mesh, + const aabb& bounding_box, + const components::mesh_vertex::flags mesh_components, + const zgl::material_handle& material, + const material_component::flags material_components +) { + const auto mesh_id = m_mesh_renderer.add( + std::make_pair(mesh_components, material_components), + mesh, + bounding_box, + glm::identity(), + material, + m_mesh_shader_program_lookup + ); + + if (not mesh_id) + { + return std::nullopt; + } + + return asset_id(std::in_place_index_t{}, *mesh_id); +} + +std::optional instance::add_point_cloud( + const zgl::point_cloud_handle& point_cloud, + const aabb& bounding_box, + const components::point_cloud_vertex::flags point_cloud_components +) { + const auto point_cloud_id = m_point_cloud_renderer.add( + point_cloud_components, + point_cloud, + bounding_box, + glm::identity(), + m_point_cloud_shader_program_lookup + ); + + if (not point_cloud_id) + { + return std::nullopt; + } + + return asset_id(std::in_place_index_t{}, *point_cloud_id); +} + +void instance::add_shader_program( + asset_types type, zgl::shader_program_handle shader_program_handle +) { + switch (type) + { + case asset_types::mesh: + m_mesh_shader_program_lookup.add(shader_program_handle); + break; + case asset_types::point_cloud: + m_point_cloud_shader_program_lookup.add(shader_program_handle); + break; + default: + break; + } +} + +bool instance::remove(asset_id id) +{ + switch (id.index()) { + case id_mesh_index: + return m_mesh_renderer.remove(std::get(id)); + case id_point_cloud_index: + return m_point_cloud_renderer.remove(std::get(id)); + default: + return false; + } +} + +bool instance::look_at(asset_id id) +{ + auto bounding_box_opt = std::optional{ std::nullopt }; + + switch (id.index()) { + case id_mesh_index: + bounding_box_opt = m_mesh_renderer.bounding_box(std::get(id)); + break; + case id_point_cloud_index: + bounding_box_opt = m_point_cloud_renderer.bounding_box(std::get(id)); + break; + default: + return false; + } + + if (bounding_box_opt) + { + const auto& bounding_box = *bounding_box_opt; + ztu::logger::debug("aabb: % %", glm::to_string(bounding_box.min), glm::to_string(bounding_box.max)); + //m_camera.look_at(bounding_box.max, bounding_box.min, m_view); + + m_camera.look_at(bounding_box.center(), bounding_box.min, m_view); + + ztu::logger::debug("pos: % front: % right: % up: % as: % fov: %", + glm::to_string(m_view.position), + glm::to_string(m_view.front), + glm::to_string(m_view.right), + glm::to_string(m_view.up), + m_view.aspect_ratio, + m_view.fov + ); + + } + + return true; +} + + +void instance::run_progress( + std::mutex& lock, + std::string& title, + float& progress, + const double fps +) { + namespace chr = std::chrono; + using namespace std::chrono_literals; + using clock = chr::high_resolution_clock; + using duration_type = clock::duration; + using floating_second = chr::duration; + + sf::RectangleShape bar_background, bar_foreground; + sf::Text title_text, percent_text; + sf::Texture logo_texture, spinner_texture; + sf::Sprite logo_sprite, spinner_sprite; + + sf::Color background_color = { 0, 0, 0, 255 }; + + constexpr auto font_size = 14; + constexpr auto padding = 15; + constexpr auto spinner_size = 10.0f; + constexpr auto spinner_degrees_per_second = 270.0f; + + title_text.setFont(m_font); + title_text.setCharacterSize(font_size); + percent_text.setFont(m_font); + percent_text.setCharacterSize(font_size); + + bar_foreground.setOutlineColor(sf::Color::Transparent); + bar_foreground.setFillColor(sf::Color{ 107, 203, 119 }); + + bar_background.setOutlineColor(sf::Color::Transparent); + bar_background.setFillColor(sf::Color{ 64, 170, 87 }); + + + const auto target_frame_time = std::chrono::duration_cast(1s) / fps; + + std::stringstream percent_builder; + percent_builder << std::setw(3) << std::setfill(' '); + + logo_texture.loadFromImage(m_logo); + logo_sprite.setTexture(logo_texture); + const auto logo_scale = static_cast(m_screen_size.x) / static_cast(logo_texture.getSize().x); + logo_sprite.scale({ logo_scale, logo_scale }); + logo_sprite.setOrigin(0, 0); + logo_sprite.setPosition(0, 0); + + const auto dim = static_cast(m_screen_size); + const auto bar_dim = sf::Vector2f(dim.x - 2 * padding, 10.0f); + const auto bar_pos = sf::Vector2f((dim.x - bar_dim.x) / 2.0f, dim.y - bar_dim.y - padding); + bar_background.setPosition(bar_pos + sf::Vector2f{ 3, 3 }); + bar_background.setSize(bar_dim); + bar_foreground.setPosition(bar_pos); + + spinner_texture.loadFromImage(m_spinner); + spinner_sprite.setTexture(spinner_texture); + const auto spinner_dim = static_cast(spinner_texture.getSize().y); + const auto spinner_scale = spinner_size / spinner_dim; + spinner_sprite.scale({ spinner_scale, spinner_scale }); + spinner_sprite.setOrigin(spinner_dim / 2.0f, spinner_dim / 2.0f); + spinner_sprite.setPosition(padding + spinner_size / 2.0f, bar_pos.y - 0.5f * padding - spinner_size / 2.0f); + + const auto title_text_pos = sf::Vector2f(1.5f * padding + spinner_size, bar_pos.y - 0.5f * padding - font_size); + const auto percent_text_pos = sf::Vector2f(dim.x - padding, bar_pos.y - 0.5f * padding - font_size); + title_text.setPosition(title_text_pos); + percent_text.setPosition(percent_text_pos); + + percent_text.setFillColor(sf::Color::White); + title_text.setFillColor(sf::Color::White); + + const auto start_time = clock::now(); + + while (true) + { + const auto frame_begin = clock::now(); + + const auto t = chr::duration_cast(frame_begin - start_time).count(); + + lock.lock(); + + auto curr_progress = progress; + title_text.setString(title); + + lock.unlock(); + + if (curr_progress == std::numeric_limits::max()) + { + break; + } + + m_window.clear(background_color); + + m_window.draw(logo_sprite); + m_window.draw(bar_background); + + bar_foreground.setSize(sf::Vector2f(curr_progress * bar_dim.x, bar_dim.y)); + m_window.draw(bar_foreground); + + const auto spinner_angle = t * spinner_degrees_per_second; + spinner_sprite.setRotation(spinner_angle); + m_window.draw(spinner_sprite); + + const auto percent = static_cast(std::round(100.0f * curr_progress)); + + percent_builder.str(""); + percent_builder << percent << '%'; + + percent_text.setString(percent_builder.str()); + const auto percent_text_bounds = percent_text.getLocalBounds(); + percent_text.setOrigin( + percent_text_bounds.left + percent_text_bounds.width, + 0 + ); + + m_window.draw(title_text); + m_window.draw(percent_text); + m_window.display(); + + const auto time_taken = clock::now() - frame_begin; + if (time_taken < target_frame_time) + { + std::this_thread::sleep_for(target_frame_time - time_taken); + } + } +} + +void instance::run(std::mutex& gl_resource_lock, const double fps) +{ + namespace chr = std::chrono; + using namespace std::chrono_literals; + using clock = chr::high_resolution_clock; + using duration_type = clock::duration; + using floating_second = chr::duration; + + const auto target_frame_time = std::chrono::duration_cast(1s) / fps; + + auto frame_begin = clock::now(); + + m_mouse_locked = true; + m_window.setMouseCursorVisible(false); + + while (true) + { + auto prev_frame_begin = frame_begin; + frame_begin = clock::now(); + + const auto dt = chr::duration_cast(frame_begin - prev_frame_begin); + + if (not update(dt.count())) break; + + gl_resource_lock.lock(); + render(); + gl_resource_lock.unlock(); + + /*ztu::logger::debug("pos: % front: % right: % up: % as: % fov: %", + glm::to_string(m_view.position), + glm::to_string(m_view.front), + glm::to_string(m_view.right), + glm::to_string(m_view.up), + m_view.aspect_ratio, + m_view.fov + );*/ + + const auto time_taken = clock::now() - frame_begin; + if (time_taken < target_frame_time) + { + std::this_thread::sleep_for(target_frame_time - time_taken); + } + } +} + +void instance::windowed(const unsigned int width, const unsigned int height, const bool decorations) { + + m_window.create( + sf::VideoMode(width, height), + m_title, + decorations ? sf::Style::Default : sf::Style::None, + m_context_settings + ); + + m_screen_size = { width, height }; + + m_view.aspect_ratio = static_cast(width) / static_cast(height); +} + +void instance::fullscreen() +{ + m_window.create( + sf::VideoMode(), + m_title, + sf::Style::Fullscreen, + m_context_settings + ); +} + + +void instance::size(const unsigned int width, const unsigned int height) { + + m_window.setSize({ width, height }); + + m_screen_size = { width, height }; + + m_view.aspect_ratio = static_cast(width) / static_cast(height); +} + +bool instance::update(const double dt) { + + //ztu::logger::log("pos: % dir: %", glm::to_string(m_view.position), glm::to_string(m_view.front)); + + auto mouse_pos_delta = glm::vec2{ 0, 0 }; + auto mouse_wheel_delta = 0.0f; + + sf::Event event; + while (m_window.pollEvent(event)) + { + switch (event.type) { + case sf::Event::Closed: [[unlikely]] + { + return false; + } + case sf::Event::Resized: + { + const auto& [ width, height ] = event.size; + glViewport(0, 0, width, height); + m_screen_size = { width, height }; + break; + } + case sf::Event::MouseWheelMoved: + { + mouse_wheel_delta = m_settings.scroll_speed * event.mouseWheel.delta; + break; + } + case sf::Event::KeyPressed: + switch (event.key.code) { + case sf::Keyboard::Escape: + return false; + case sf::Keyboard::Tab: + m_window.setMouseCursorVisible(m_mouse_locked); + m_mouse_locked = not m_mouse_locked; + break; + case sf::Keyboard::T: + //renderIndex = (renderIndex + 1) % renderers.size(); + break; + case sf::Keyboard::C: + //renderIndex = (renderIndex + 1) % renderers.size(); + break; + default: + break; + } + break; + default: + break; + } + } + + m_view.aspect_ratio = static_cast(m_screen_size.x) / static_cast(m_screen_size.y); + + if (m_mouse_locked) [[likely]] + { + const auto screen_center = m_screen_size / 2; + + const auto [ mouse_x, mouse_y ] = sf::Mouse::getPosition(m_window); + sf::Mouse::setPosition({ screen_center.x, screen_center.y }, m_window); + + const auto mouse_pixel_delta = glm::ivec2{ mouse_x, mouse_y } - screen_center; + + mouse_pos_delta = m_settings.mouse_sensitivity * static_cast(mouse_pixel_delta); + + m_camera.update( + static_cast(dt), + mouse_pos_delta, + mouse_wheel_delta, + m_view + ); + } + + return true; +} + +void instance::render() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + const auto view_matrix = m_view.create_view_matrix(); + const auto projection_matrix = m_view.create_projection_matrix(); + + const auto vp_matrix = projection_matrix * view_matrix; + + m_mesh_renderer.render( + m_mesh_render_mode, + vp_matrix, + view_matrix, + m_view.position, + m_settings.lighting + ); + + m_point_cloud_renderer.render( + m_point_cloud_render_mode, + vp_matrix, + m_view.position, + m_settings.lighting + ); + + m_window.display(); + + //std::cout << "f\n"; +} + + +}