From 9599029c3d9e32e0397bd11f669f3e9567ffaf3e Mon Sep 17 00:00:00 2001 From: L-Nafaryus Date: Mon, 13 Mar 2023 22:27:09 +0500 Subject: [PATCH] check it out --- .gitignore | 8 +- CMakeLists.txt | 200 ++-- README.md | 36 + cmake/external/glad.cmake | 46 +- cmake/external/glfw.cmake | 51 +- cmake/external/glm.cmake | 5 + cmake/external/googletest.cmake | 3 +- cmake/external/imgui.cmake | 182 ++-- cmake/external/implot.cmake | 73 ++ cmake/external/occt.cmake | 143 ++- cmake/external/onetbb.cmake | 8 + cmake/external/openxlsx.cmake | 8 + cmake/external/stb.cmake | 99 +- cmake/hpr-macros.cmake | 137 +++ .../Config.cmake.in} | 12 +- cmake/templates/cmake_uninstall.cmake.in | 29 + cmake/toolchains/linux-gcc.cmake | 10 + cmake/toolchains/mingw-gcc.cmake | 12 + docs/CMakeLists.txt | 44 +- docs/api/Doxyfile.in | 778 +++++++------- source/applications/CMakeLists.txt | 3 +- source/applications/fes/CMakeLists.txt | 29 + source/applications/fes/fes.cpp | 152 +++ source/applications/fes/fes.xlsx | Bin 0 -> 465904 bytes source/applications/periodic/CMakeLists.txt | 32 +- source/applications/periodic/lattice.hpp | 408 ++++---- source/applications/periodic/periodic.cpp | 486 ++++----- source/creator/CMakeLists.txt | 68 +- source/creator/camera.hpp | 135 +++ source/creator/cmake/mingw-pre-pack.cmake.in | 12 + source/creator/drawable.hpp | 238 +++++ source/creator/entity.hpp | 80 ++ source/creator/grid.hpp | 157 +++ source/creator/ray.hpp | 134 +++ source/creator/scene.hpp | 80 ++ source/creator/shaders.hpp | 145 +++ source/creator/test.cpp | 536 +++++----- source/creator/test2-back.cpp | 551 ++++++++++ source/creator/test2.cpp | 452 +++++--- source/creator/transform.hpp | 184 ++++ source/creator/ui.hpp | 157 +++ source/creator/visual.hpp | 78 ++ source/hpr/CMakeLists.txt | 61 +- source/hpr/containers.hpp | 6 +- source/hpr/containers/CMakeLists.txt | 83 +- source/hpr/containers/array.hpp | 9 +- source/hpr/containers/array/dynamic_array.hpp | 722 ++++++------- source/hpr/containers/array/iterator.hpp | 193 ++-- source/hpr/containers/array/sequence.hpp | 314 ++++++ source/hpr/containers/array/static_array.hpp | 407 +++----- source/hpr/containers/graph.hpp | 3 + source/hpr/containers/graph/tree_node.hpp | 180 ++++ source/hpr/containers/tests/CMakeLists.txt | 15 - .../hpr/containers/tests/containers-test.cpp | 130 ++- source/hpr/core.hpp | 0 source/hpr/core/CMakeLists.txt | 55 - source/hpr/core/common.hpp | 5 - source/hpr/csg.hpp | 24 +- source/hpr/csg/CMakeLists.txt | 110 +- source/hpr/csg/compound.hpp | 90 +- source/hpr/csg/edge.hpp | 78 +- source/hpr/csg/face.hpp | 84 +- source/hpr/csg/geometry.hpp | 40 +- source/hpr/csg/shape.cpp | 54 +- source/hpr/csg/shape.hpp | 980 +++++++++--------- source/hpr/csg/shell.hpp | 104 +- source/hpr/csg/solid.hpp | 90 +- source/hpr/csg/surface.hpp | 128 +-- source/hpr/csg/tests/CMakeLists.txt | 14 - source/hpr/csg/tests/csg-test.cpp | 47 +- source/hpr/csg/vertex.hpp | 86 +- source/hpr/csg/wire.hpp | 106 +- source/hpr/exception.hpp | 54 + source/hpr/geometry.hpp | 10 +- source/hpr/geometry/CMakeLists.txt | 54 +- source/hpr/geometry/polytope.hpp | 94 +- source/hpr/geometry/tetrahedron.hpp | 38 +- source/hpr/geometry/triangle.hpp | 30 +- source/hpr/gpu.hpp | 33 +- source/hpr/gpu/CMakeLists.txt | 100 +- source/hpr/gpu/array_object.cpp | 2 - source/hpr/gpu/array_object.hpp | 229 ++-- source/hpr/gpu/buffer_object.cpp | 27 - source/hpr/gpu/buffer_object.hpp | 285 ++--- source/hpr/gpu/camera.hpp | 78 +- source/hpr/gpu/color_buffer.hpp | 138 +-- source/hpr/gpu/common.hpp | 8 +- source/hpr/gpu/context.hpp | 122 +++ source/hpr/gpu/cull_face.hpp | 156 +-- source/hpr/gpu/depth_buffer.hpp | 168 +-- source/hpr/gpu/framebuffer.hpp | 307 +++--- source/hpr/gpu/gpu.cpp | 3 - source/hpr/gpu/monitor.hpp | 34 + source/hpr/gpu/renderbuffer.hpp | 138 +-- source/hpr/gpu/scene.hpp | 8 - source/hpr/gpu/shader.hpp | 256 ++--- source/hpr/gpu/shader_program.hpp | 364 +++++-- source/hpr/gpu/shaders/test.frag.glsl | 16 +- source/hpr/gpu/shaders/test.vert.glsl | 20 +- source/hpr/gpu/stencil_buffer.hpp | 168 +-- source/hpr/gpu/texture.hpp | 416 ++++---- source/hpr/gpu/viewer.hpp | 8 - source/hpr/gpu/viewport.hpp | 99 +- source/hpr/gpu/window.hpp | 296 ++++++ source/hpr/hpr.cpp | 2 +- source/hpr/hpr.hpp | 86 +- source/hpr/io.hpp | 6 +- source/hpr/io/CMakeLists.txt | 95 +- source/hpr/io/file.cpp | 138 +-- source/hpr/io/file.hpp | 106 +- source/hpr/io/logger.hpp | 828 ++++++++++----- source/hpr/io/tests/CMakeLists.txt | 14 - source/hpr/io/tests/io-test.cpp | 3 +- source/hpr/math.hpp | 11 +- source/hpr/math/CMakeLists.txt | 61 +- source/hpr/math/integer.hpp | 8 +- source/hpr/math/integer/integer.hpp | 32 +- source/hpr/math/integer/size.hpp | 42 +- source/hpr/math/matrix.hpp | 6 +- source/hpr/math/matrix/clip_space.hpp | 106 +- .../matrix/{matrix_space.hpp => matrix.hpp} | 754 +++++++------- source/hpr/math/matrix/transform.hpp | 191 ++-- source/hpr/math/quaternion.hpp | 6 +- source/hpr/math/quaternion/quaternion.hpp | 548 +++++----- source/hpr/math/scalar.hpp | 6 +- source/hpr/math/scalar/scalar.hpp | 119 ++- source/hpr/math/tests/CMakeLists.txt | 14 - source/hpr/math/tests/math-test.cpp | 12 +- source/hpr/math/vector.hpp | 2 +- .../vector/{vector_space.hpp => vector.hpp} | 656 ++++++------ source/hpr/math/vector/vector_space.cpp | 4 - source/hpr/mesh.hpp | 6 +- source/hpr/mesh/CMakeLists.txt | 69 +- source/hpr/mesh/cell.hpp | 74 +- source/hpr/mesh/edge.hpp | 130 +-- source/hpr/mesh/face.hpp | 142 +-- source/hpr/mesh/mesh.cpp | 2 +- source/hpr/mesh/mesh.hpp | 456 ++++---- source/hpr/mesh/tests/CMakeLists.txt | 14 - source/hpr/mesh/tests/hmesh-test.cpp | 2 +- source/hpr/mesh/vertex.hpp | 98 +- source/hpr/parallel.hpp | 3 + source/hpr/parallel/CMakeLists.txt | 24 + source/hpr/parallel/parallel.hpp | 7 + source/hpr/window_system.hpp | 11 - source/hpr/window_system/CMakeLists.txt | 68 -- source/hpr/window_system/glfw/monitor.cpp | 14 - source/hpr/window_system/glfw/monitor.hpp | 28 - source/hpr/window_system/glfw/window.cpp | 100 -- source/hpr/window_system/glfw/window.hpp | 45 - .../hpr/window_system/glfw/window_system.cpp | 36 - .../hpr/window_system/glfw/window_system.hpp | 22 - source/hpr/window_system/monitor.cpp | 55 - source/hpr/window_system/monitor.hpp | 84 -- source/hpr/window_system/window.cpp | 118 --- source/hpr/window_system/window.hpp | 123 --- source/hpr/window_system/window_context.hpp | 44 - source/hpr/window_system/window_system.cpp | 65 -- source/hpr/window_system/window_system.hpp | 62 -- 159 files changed, 11334 insertions(+), 8402 deletions(-) create mode 100644 cmake/external/glm.cmake create mode 100644 cmake/external/implot.cmake create mode 100644 cmake/external/onetbb.cmake create mode 100644 cmake/external/openxlsx.cmake create mode 100644 cmake/hpr-macros.cmake rename cmake/{hprConfig.cmake.in => templates/Config.cmake.in} (57%) create mode 100644 cmake/templates/cmake_uninstall.cmake.in create mode 100644 cmake/toolchains/linux-gcc.cmake create mode 100644 cmake/toolchains/mingw-gcc.cmake create mode 100644 source/applications/fes/CMakeLists.txt create mode 100644 source/applications/fes/fes.cpp create mode 100644 source/applications/fes/fes.xlsx create mode 100644 source/creator/camera.hpp create mode 100644 source/creator/cmake/mingw-pre-pack.cmake.in create mode 100644 source/creator/drawable.hpp create mode 100644 source/creator/entity.hpp create mode 100644 source/creator/grid.hpp create mode 100644 source/creator/ray.hpp create mode 100644 source/creator/scene.hpp create mode 100644 source/creator/shaders.hpp create mode 100644 source/creator/test2-back.cpp create mode 100644 source/creator/transform.hpp create mode 100644 source/creator/ui.hpp create mode 100644 source/creator/visual.hpp create mode 100644 source/hpr/containers/array/sequence.hpp create mode 100644 source/hpr/containers/graph.hpp create mode 100644 source/hpr/containers/graph/tree_node.hpp delete mode 100644 source/hpr/containers/tests/CMakeLists.txt delete mode 100644 source/hpr/core.hpp delete mode 100644 source/hpr/core/CMakeLists.txt delete mode 100644 source/hpr/core/common.hpp delete mode 100644 source/hpr/csg/tests/CMakeLists.txt create mode 100644 source/hpr/exception.hpp delete mode 100644 source/hpr/gpu/array_object.cpp delete mode 100644 source/hpr/gpu/buffer_object.cpp create mode 100644 source/hpr/gpu/context.hpp create mode 100644 source/hpr/gpu/monitor.hpp create mode 100644 source/hpr/gpu/window.hpp delete mode 100644 source/hpr/io/tests/CMakeLists.txt rename source/hpr/math/matrix/{matrix_space.hpp => matrix.hpp} (81%) delete mode 100644 source/hpr/math/tests/CMakeLists.txt rename source/hpr/math/vector/{vector_space.hpp => vector.hpp} (95%) delete mode 100644 source/hpr/math/vector/vector_space.cpp delete mode 100644 source/hpr/mesh/tests/CMakeLists.txt create mode 100644 source/hpr/parallel.hpp create mode 100644 source/hpr/parallel/CMakeLists.txt create mode 100644 source/hpr/parallel/parallel.hpp delete mode 100644 source/hpr/window_system.hpp delete mode 100644 source/hpr/window_system/CMakeLists.txt delete mode 100644 source/hpr/window_system/glfw/monitor.cpp delete mode 100644 source/hpr/window_system/glfw/monitor.hpp delete mode 100644 source/hpr/window_system/glfw/window.cpp delete mode 100644 source/hpr/window_system/glfw/window.hpp delete mode 100644 source/hpr/window_system/glfw/window_system.cpp delete mode 100644 source/hpr/window_system/glfw/window_system.hpp delete mode 100644 source/hpr/window_system/monitor.cpp delete mode 100644 source/hpr/window_system/monitor.hpp delete mode 100644 source/hpr/window_system/window.cpp delete mode 100644 source/hpr/window_system/window.hpp delete mode 100644 source/hpr/window_system/window_context.hpp delete mode 100644 source/hpr/window_system/window_system.cpp delete mode 100644 source/hpr/window_system/window_system.hpp diff --git a/.gitignore b/.gitignore index a207396..a6fc926 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -cmake-wsl-build-debug -cmake-build-debug/ +cmake-build*/ +build*/ + .idea/ -build/ + .ccls-cache/ compile_commands.json .cache/ + temp/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 20325b5..5b75236 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,34 @@ cmake_minimum_required (VERSION 3.16) +# +set(HPR_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +set(HPR_EXTERNAL_PATH "${HPR_MODULE_PATH}/external") +set(HPR_TOOLCHAINS_PATH "${HPR_MODULE_PATH}/toolchains") + +set(HPR_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/source") + +# toolchain +#if (MINGW) +# set(CMAKE_TOOLCHAIN_FILE "${HPR_TOOLCHAINS_PATH}/mingw-gcc.cmake") +#else() +# set(CMAKE_TOOLCHAIN_FILE "${HPR_TOOLCHAINS_PATH}/linux-gcc.cmake") +#endif() + # Extract project version from source -file(STRINGS "source/hpr/core/common.hpp" - hpr_version_defines REGEX "#define HPR_VERSION_(MAJOR|MINOR|PATCH) ") +include(${HPR_MODULE_PATH}/hpr-macros.cmake) -foreach(ver ${hpr_version_defines}) - if(ver MATCHES [[#define HPR_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$]]) - set(HPR_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") - endif() -endforeach() - -if(HPR_VERSION_PATCH MATCHES [[\.([a-zA-Z0-9]+)$]]) - set(hpr_VERSION_TYPE "${CMAKE_MATCH_1}") -endif() - -string(REGEX MATCH "^[0-9]+" HPR_VERSION_PATCH "${HPR_VERSION_PATCH}") -set(HPR_PROJECT_VERSION "${HPR_VERSION_MAJOR}.${HPR_VERSION_MINOR}.${HPR_VERSION_PATCH}") +hpr_parse_version("${HPR_SOURCE_DIR}/hpr/hpr.hpp" "#define HPR_VERSION_(MAJOR|MINOR|PATCH)") # Main project project( hpr - VERSION "${HPR_PROJECT_VERSION}" + VERSION "${HPR_VERSION}" LANGUAGES CXX ) +# Compiler settings +set(CMAKE_CXX_STANDARD 20) + # Detect how project is used if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) @@ -35,133 +40,124 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) message(AUTHOR_WARNING ${lines}) endif() - set(HPR_MASTER_PROJECT ON) - set(CMAKE_CXX_STANDARD 20) - - message(STATUS ${CMAKE_SOURCE_DIR}) + set(HPR_IS_TOP_LEVEL ON) else() - set(HPR_MASTER_PROJECT OFF) + set(HPR_IS_TOP_LEVEL OFF) endif() +# Project options +option(HPR_INSTALL "Install hpr files?" ${HPR_IS_TOP_LEVEL}) +option(HPR_TEST "Build hpr tests?" ${HPR_IS_TOP_LEVEL}) - -# Standard includes +# Installation settings include(GNUInstallDirs) -include(CPack) -# Options -option(HPR_INSTALL "Install hpr files?" ${HPR_MASTER_PROJECT}) -option(HPR_TEST "Build hpr tests?" ${HPR_MASTER_PROJECT}) +set(HPR_INSTALL_INTERFACE "$' not included + +# Package manager +include(${HPR_MODULE_PATH}/tools/CPM.cmake) # Testing if(HPR_TEST) enable_testing() - option(INSTALL_GMOCK "" OFF) - option(INSTALL_GTEST "" OFF) - - include(${CMAKE_SOURCE_DIR}/cmake/external/googletest.cmake) + include("${HPR_EXTERNAL_PATH}/googletest.cmake") include(GoogleTest) endif() -# Modules -option(WITH_CONTAINERS "" ON) -option(WITH_MATH "" ON) -option(WITH_IO "" ON) -option(WITH_MESH "" ON) -option(WITH_CSG "" ON) - option(USE_SYSTEM_OCCT "" ON) -option(WITH_GPU "" ON) -option(WITH_WINDOW_SYSTEM "" ON) +# Uninstall target +if (NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/cmake/templates/cmake_uninstall.cmake.in" cmake_uninstall.cmake + IMMEDIATE @ONLY + ) -set_property(GLOBAL PROPERTY HPR_MODULES "") -macro(add_module MODULE) - set_property(GLOBAL APPEND PROPERTY HPR_MODULES "${MODULE}") -endmacro() + add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake") +endif() + + +# Modules +option(HPR_WITH_CONTAINERS "" ON) +option(HPR_WITH_MATH "" ON) +option(HPR_WITH_IO "" ON) +option(HPR_WITH_MESH "" ON) +option(HPR_WITH_CSG "" ON) +option(HPR_WITH_GPU "" ON) +option(HPR_WITH_PARALLEL "" ON) + +include_directories(${HPR_SOURCE_DIR}) add_subdirectory(source/hpr) -get_property(hpr_modules GLOBAL PROPERTY HPR_MODULES) -foreach(module ${hpr_modules}) - get_target_property(module_type ${module} TYPE) - if(module_type STREQUAL "INTERFACE_LIBRARY") - list(APPEND HPR_INTERFACE_LIBS "${PROJECT_NAME}::${module}") - else() - list(APPEND HPR_PUBLIC_LIBS "${PROJECT_NAME}::${module}") - endif() - set(hpr_modules_summary "${hpr_modules_summary} ${module}") -endforeach() +hpr_collect_modules() # Main library -add_library(hpr SHARED - source/hpr/hpr.cpp) +add_library(hpr SHARED source/hpr/hpr.cpp) add_library(hpr::hpr ALIAS hpr) target_sources(hpr - INTERFACE - "$" - "$" + INTERFACE "$" "${HPR_INSTALL_INTERFACE}>" ) target_link_libraries(hpr - INTERFACE - ${HPR_INTERFACE_LIBS} - PUBLIC - ${HPR_PUBLIC_LIBS} + INTERFACE ${HPR_INTERFACE_LIBS} + PUBLIC ${HPR_PUBLIC_LIBS} ) -include(CMakePackageConfigHelpers) -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - INSTALL_DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}-${PROJECT_VERSION} -) +# Installation +if (HPR_INSTALL) + include(CMakePackageConfigHelpers) -write_basic_package_version_file( - "${PROJECT_NAME}ConfigVersion.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) + configure_package_config_file( + "${HPR_MODULE_PATH}/templates/Config.cmake.in" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + ) -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}-${PROJECT_VERSION} -) -install( + write_basic_package_version_file( + "${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion + ) + + install(FILES + ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} COMPONENT devel + ) + + install( TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} COMPONENT devel + ) + + install( EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} COMPONENT devel NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) + ) + install( + FILES source/${PROJECT_NAME}/${PROJECT_NAME}.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} COMPONENT devel + ) + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_PACKAGE_VERSION "${HPR_VERSION}") + + include(CPack) + cpack_add_component(devel DISPLAY_NAME "Development") + cpack_add_component(runtime DISPLAY_NAME "Runtime libraries") +endif() # Project summary -set(summary - "Summary:\n" - "Version: ${HPR_PROJECT_VERSION}\n" - "Master: ${HPR_MASTER_PROJECT}\n" - "Modules: ${hpr_modules_summary}" -) -message(STATUS ${summary}) +hpr_print_summary() # Documentation #add_subdirectory(docs) # Additional applications +add_subdirectory(source/creator) add_subdirectory(source/applications) -add_subdirectory(source/creator) \ No newline at end of file diff --git a/README.md b/README.md index e69de29..5c7ec7a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,36 @@ +# hpr + +## Installation from sources + +### Prerequisites + +- CMake version 3.16 (or newer) + +### Configure + +``` +cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -B cmake-build-debug -S . +``` + +### Build + +``` +cmake --build cmake-build-debug --target hpr +``` + +### Install + +``` +cmake --install cmake-build-debug --component devel +``` + +The following install components are supported: +- `runtime` - runtime package (core shared libraries and `.dll` files on Windows* OS). +- `devel` - development package (header files, CMake integration files, library symbolic links, and `.lib` files on Windows* OS). + +### Pack + +``` +cd cmake-build-debug[.gitignore](.gitignore) +cpack +``` \ No newline at end of file diff --git a/cmake/external/glad.cmake b/cmake/external/glad.cmake index 663275b..785424d 100644 --- a/cmake/external/glad.cmake +++ b/cmake/external/glad.cmake @@ -1,4 +1,3 @@ -include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) CPMAddPackage( NAME glad @@ -6,10 +5,45 @@ CPMAddPackage( #VERSION 2.0.2 VERSION 0.1.36 GIT_PROGRESS TRUE - OPTIONS "GLAD_EXPORT ON" "GLAD_INSTALL ON" + EXCLUDE_FROM_ALL ON + OPTIONS "GLAD_EXPORT OFF" "GLAD_INSTALL OFF" ) -#if(glad_ADDED) -# add_subdirectory("${glad_SOURCE_DIR}/cmake" glad_cmake) -# glad_add_library(glad REPRODUCIBLE API gl:core=3.3) -#endif() \ No newline at end of file +if(glad_ADDED) + set(EXTERNAL_PROJECT_NAME glad) + + include(GNUInstallDirs) + + add_library(glad::glad ALIAS glad) + + install( + TARGETS ${EXTERNAL_PROJECT_NAME} + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT devel + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT devel + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT devel + ) + + install( + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake + NAMESPACE ${EXTERNAL_PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + ) + + install( + DIRECTORY ${glad_BINARY_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT devel + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) + +endif() \ No newline at end of file diff --git a/cmake/external/glfw.cmake b/cmake/external/glfw.cmake index b8c033f..9b5d6cd 100644 --- a/cmake/external/glfw.cmake +++ b/cmake/external/glfw.cmake @@ -1,8 +1,43 @@ -include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) - -CPMAddPackage( - NAME glfw - GIT_REPOSITORY https://github.com/glfw/glfw.git - GIT_TAG 3.3.7 - EXCLUDE_FROM_ALL ON -) \ No newline at end of file + +CPMAddPackage( + NAME glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG 3.3.7 + #EXCLUDE_FROM_ALL ON + OPTIONS "GLFW_INSTALL OFF" "GLFW_BUILD_EXAMPLES OFF" "GLFW_BUILD_TESTS OFF" +) + +if(glad_ADDED) + set(EXTERNAL_PROJECT_NAME glfw) + + include(GNUInstallDirs) + + add_library(glfw::glfw ALIAS glfw) + + install(DIRECTORY "${glfw_SOURCE_DIR}/include/GLFW" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT devel + FILES_MATCHING PATTERN glfw3.h PATTERN glfw3native.h) + + install(FILES "${glfw_BINARY_DIR}/src/glfw3Config.cmake" + "${glfw_BINARY_DIR}/src/glfw3ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/glfw3 + COMPONENT devel) + + install(EXPORT glfwTargets FILE glfw3Targets.cmake + EXPORT_LINK_INTERFACE_LIBRARIES + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/glfw3 + COMPONENT devel) + + install(FILES "${glfw_BINARY_DIR}/src/glfw3.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + COMPONENT devel) + install( + TARGETS ${EXTERNAL_PROJECT_NAME} + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + RUNTIME COMPONENT runtime + LIBRARY COMPONENT devel + ARCHIVE COMPONENT devel + INCLUDES COMPONENT devel + ) + +endif() diff --git a/cmake/external/glm.cmake b/cmake/external/glm.cmake new file mode 100644 index 0000000..0b34c4e --- /dev/null +++ b/cmake/external/glm.cmake @@ -0,0 +1,5 @@ +CPMAddPackage( + NAME glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG 0.9.9.8 +) \ No newline at end of file diff --git a/cmake/external/googletest.cmake b/cmake/external/googletest.cmake index 29690e7..1f01338 100644 --- a/cmake/external/googletest.cmake +++ b/cmake/external/googletest.cmake @@ -1,7 +1,8 @@ -include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) CPMAddPackage( NAME googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.12.1 + EXCLUDE_FROM_ALL ON + OPTIONS "BUILD_GMOCK OFF" "INSTALL_GTEST OFF" ) diff --git a/cmake/external/imgui.cmake b/cmake/external/imgui.cmake index a27e963..87d8401 100644 --- a/cmake/external/imgui.cmake +++ b/cmake/external/imgui.cmake @@ -1,86 +1,96 @@ -include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) - -# branch: docking -CPMAddPackage( - NAME imgui_external - GIT_REPOSITORY https://github.com/ocornut/imgui.git - GIT_TAG 24dfebf455ac1f7685e1a72272d37b72601fe70c - DOWNLOAD_ONLY YES -) - -if(imgui_external_ADDED) - set(EXTERNAL_PROJECT_NAME imgui) - - set(CMAKE_CXX_STANDARD 17) - - find_package(PkgConfig REQUIRED) - find_package(Freetype REQUIRED) - find_package(OpenGL REQUIRED) - pkg_search_module(GLFW REQUIRED glfw3) - - add_library(${EXTERNAL_PROJECT_NAME} STATIC - ${imgui_external_SOURCE_DIR}/imgui.cpp - ${imgui_external_SOURCE_DIR}/imgui_demo.cpp - ${imgui_external_SOURCE_DIR}/imgui_draw.cpp - ${imgui_external_SOURCE_DIR}/imgui_tables.cpp - ${imgui_external_SOURCE_DIR}/imgui_widgets.cpp - - ${imgui_external_SOURCE_DIR}/backends/imgui_impl_glfw.cpp - ${imgui_external_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp - - ${imgui_external_SOURCE_DIR}/misc/freetype/imgui_freetype.cpp - ${imgui_external_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp - ) - add_library(imgui::imgui ALIAS ${EXTERNAL_PROJECT_NAME}) - - add_compile_definitions(IMGUI_IMPL_OPENGL_LOADER_GLAD) - - target_include_directories(${EXTERNAL_PROJECT_NAME} - PUBLIC - $ - $ - $ - $ - - ${FREETYPE_INCLUDE_DIRS} - ${GLFW_INCLUDE_DIRS} - ) - - target_link_libraries(${EXTERNAL_PROJECT_NAME} - PUBLIC - Freetype::Freetype - glfw - OpenGL::GL - ) - - set_target_properties(${EXTERNAL_PROJECT_NAME} - PROPERTIES - POSITION_INDEPENDENT_CODE ON - OUTPUT_NAME imgui - EXCLUDE_FROM_ALL ON - ) - - include(GNUInstallDirs) - - install( - TARGETS ${EXTERNAL_PROJECT_NAME} - EXPORT ${EXTERNAL_PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${EXTERNAL_PROJECT_NAME} - ) - - install( - EXPORT ${EXTERNAL_PROJECT_NAME}Targets - FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake - NAMESPACE ${EXTERNAL_PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} - ) - - install( - DIRECTORY ${imgui_external_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${EXTERNAL_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - ) -endif() + +# branch: docking +CPMAddPackage( + NAME imgui_external + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG 24dfebf455ac1f7685e1a72272d37b72601fe70c + DOWNLOAD_ONLY YES +) + +if(imgui_external_ADDED) + set(EXTERNAL_PROJECT_NAME imgui) + + set(CMAKE_CXX_STANDARD 17) + + find_package(PkgConfig REQUIRED) + find_package(Freetype REQUIRED) + find_package(OpenGL REQUIRED) + find_package(GLFW REQUIRED) + + add_library(${EXTERNAL_PROJECT_NAME} + ${imgui_external_SOURCE_DIR}/imgui.cpp + ${imgui_external_SOURCE_DIR}/imgui_demo.cpp + ${imgui_external_SOURCE_DIR}/imgui_draw.cpp + ${imgui_external_SOURCE_DIR}/imgui_tables.cpp + ${imgui_external_SOURCE_DIR}/imgui_widgets.cpp + + ${imgui_external_SOURCE_DIR}/backends/imgui_impl_glfw.cpp + ${imgui_external_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp + + ${imgui_external_SOURCE_DIR}/misc/freetype/imgui_freetype.cpp + ${imgui_external_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp + ) + add_library(imgui::imgui ALIAS ${EXTERNAL_PROJECT_NAME}) + + add_compile_definitions(IMGUI_IMPL_OPENGL_LOADER_GLAD) + + target_include_directories(${EXTERNAL_PROJECT_NAME} + PUBLIC + $ + $ + $ + $ + + ${FREETYPE_INCLUDE_DIRS} + ${GLFW_INCLUDE_DIRS} + ) + + target_link_libraries(${EXTERNAL_PROJECT_NAME} + PUBLIC + Freetype::Freetype + glfw + OpenGL::GL + ) + + set_target_properties(${EXTERNAL_PROJECT_NAME} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME imgui + EXCLUDE_FROM_ALL ON + ) + + if(IMGUI_INSTALL) + include(GNUInstallDirs) + + install( + TARGETS ${EXTERNAL_PROJECT_NAME} + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT devel + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT devel + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT devel + + ) + + install( + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake + NAMESPACE ${EXTERNAL_PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + ) + + install( + DIRECTORY ${imgui_external_SOURCE_DIR}/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) + endif() +endif() diff --git a/cmake/external/implot.cmake b/cmake/external/implot.cmake new file mode 100644 index 0000000..d88212d --- /dev/null +++ b/cmake/external/implot.cmake @@ -0,0 +1,73 @@ + +CPMAddPackage( + NAME implot_external + GIT_REPOSITORY https://github.com/epezent/implot.git + GIT_TAG v0.14 + DOWNLOAD_ONLY YES +) + +if(implot_external_ADDED) + set(EXTERNAL_PROJECT_NAME implot) + + set(CMAKE_CXX_STANDARD 17) + + find_package(PkgConfig REQUIRED) + find_package(Freetype REQUIRED) + find_package(OpenGL REQUIRED) + find_package(GLFW REQUIRED) + + add_library(${EXTERNAL_PROJECT_NAME} + ${implot_external_SOURCE_DIR}/implot.cpp + ${implot_external_SOURCE_DIR}/implot_demo.cpp + ${implot_external_SOURCE_DIR}/implot_items.cpp + ) + add_library(implot::implot ALIAS ${EXTERNAL_PROJECT_NAME}) + + + target_include_directories(${EXTERNAL_PROJECT_NAME} + PUBLIC + $ + ) + + target_link_libraries(${EXTERNAL_PROJECT_NAME} + PUBLIC + imgui::imgui + ) + + set_target_properties(${EXTERNAL_PROJECT_NAME} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME implot + EXCLUDE_FROM_ALL ON + ) + + if(IMPLOT_INSTALL) + include(GNUInstallDirs) + + install( + TARGETS ${EXTERNAL_PROJECT_NAME} + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT devel + ) + + install( + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake + NAMESPACE ${EXTERNAL_PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + ) + + install( + DIRECTORY ${implot_external_SOURCE_DIR}/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) + endif() +endif() diff --git a/cmake/external/occt.cmake b/cmake/external/occt.cmake index 61f664e..35319e5 100644 --- a/cmake/external/occt.cmake +++ b/cmake/external/occt.cmake @@ -1,76 +1,67 @@ - -if(USE_SYSTEM_OCCT) - find_package(OpenCASCADE REQUIRED) - - if(OpenCASCADE_FOUND) - message(STATUS "OCCT: Found") - else() - message(FATAL "OCCT: Not Found") - endif() - -else() - include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) - - CPMAddPackage( - NAME occt - GIT_REPOSITORY https://github.com/Open-Cascade-SAS/OCCT.git - GIT_TAG V7_6_2 - DOWNLOAD_ONLY YES - ) - - if(occt_ADDED) - # They are using CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR for the project root, fix it - file(READ ${occt_SOURCE_DIR}/CMakeLists.txt filedata_) - string(FIND "${filedata_}" "CMAKE_SOURCE_DIR" need_patch) - - if(NOT ${need_patch} EQUAL -1) - string(REPLACE "CMAKE_SOURCE_DIR" "OCCT_SOURCE_DIR" filedata_ "${filedata_}") - string(REPLACE "CMAKE_BINARY_DIR" "OCCT_BINARY_DIR" filedata_ "${filedata_}") - string(REPLACE "project (OCCT)" "" filedata_ "${filedata_}") - string(PREPEND filedata_ "project(OCCT)\nset(OCCT_BINARY_DIR $\{_OCCT_BINARY_DIR\})\n") - endif() - file(WRITE ${occt_SOURCE_DIR}/CMakeLists.txt "${filedata_}") - - file(GLOB_RECURSE files_to_patch ${occt_SOURCE_DIR}/adm/cmake "occt_*") - - foreach(file_path ${files_to_patch}) - file(READ ${file_path} filedata_) - string(REPLACE "CMAKE_SOURCE_DIR" "OCCT_SOURCE_DIR" filedata_ "${filedata_}") - string(REPLACE "CMAKE_BINARY_DIR" "OCCT_BINARY_DIR" filedata_ "${filedata_}") - file(WRITE ${file_path} "${filedata_}") - endforeach() - - project(OCCT) - # find better way to pass build directory - set(_OCCT_BINARY_DIR ${occt_BINARY_DIR}) - set(INSTALL_DIR ${occt_BINARY_DIR} CACHE BOOL "" FORCE) - - set(USE_TK OFF CACHE BOOL "" FORCE) - set(USE_FREETYPE OFF CACHE BOOL "" FORCE) - set(USE_TCL OFF CACHE INTERNAL "" FORCE) - - set(BUILD_MODULE_Visualization OFF CACHE BOOL "" FORCE) - set(BUILD_MODULE_ApplicationFramework OFF CACHE BOOL "" FORCE) - set(BUILD_MODULE_Draw OFF CACHE BOOL "" FORCE) - - add_subdirectory(${occt_SOURCE_DIR}) - endif() -endif() - -set(OCCT_LIBRARIES - TKernel - TKService - TKV3d - TKOpenGl - TKBRep - TKBool - TKFillet - TKGeomBase - TKGeomAlgo - TKG3d - TKG2d - TKTopAlgo - TKPrim - TKSTEP - ) -set(OCCT_INCLUDE_DIRS ${OpenCASCADE_INCLUDE_DIR}) + +find_package(OpenCASCADE QUIET) + +if (NOT OpenCASCADE_FOUND) + CPMAddPackage( + NAME OpenCASCADE + GIT_REPOSITORY https://github.com/Open-Cascade-SAS/OCCT.git + GIT_TAG V7_6_2 + DOWNLOAD_ONLY YES + ) + + if(OpenCASCADE_ADDED) + # They are using CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR for the project root, fix it + file(READ ${OpenCASCADE_SOURCE_DIR}/CMakeLists.txt filedata_) + string(FIND "${filedata_}" "CMAKE_SOURCE_DIR" need_patch) + + if(NOT ${need_patch} EQUAL -1) + string(REPLACE "CMAKE_SOURCE_DIR" "OCCT_SOURCE_DIR" filedata_ "${filedata_}") + string(REPLACE "CMAKE_BINARY_DIR" "OCCT_BINARY_DIR" filedata_ "${filedata_}") + string(REPLACE "project (OCCT)" "" filedata_ "${filedata_}") + string(PREPEND filedata_ "project(OCCT)\nset(OCCT_BINARY_DIR $\{_OCCT_BINARY_DIR\})\n") + endif() + file(WRITE ${OpenCASCADE_SOURCE_DIR}/CMakeLists.txt "${filedata_}") + + file(GLOB_RECURSE files_to_patch ${OpenCASCADE_SOURCE_DIR}/adm/cmake "occt_*") + + foreach(file_path ${files_to_patch}) + file(READ ${file_path} filedata_) + string(REPLACE "CMAKE_SOURCE_DIR" "OCCT_SOURCE_DIR" filedata_ "${filedata_}") + string(REPLACE "CMAKE_BINARY_DIR" "OCCT_BINARY_DIR" filedata_ "${filedata_}") + file(WRITE ${file_path} "${filedata_}") + endforeach() + + #project(OCCT) + # find better way to pass build directory + #set(_OCCT_BINARY_DIR ${OpenCASCADE_BINARY_DIR}) + set(INSTALL_DIR ${OpenCASCADE_BINARY_DIR} CACHE BOOL "" FORCE) + + set(USE_TK OFF CACHE BOOL "" FORCE) + set(USE_FREETYPE OFF CACHE BOOL "" FORCE) + set(USE_TCL OFF CACHE INTERNAL "" FORCE) + + set(BUILD_MODULE_Visualization OFF CACHE BOOL "" FORCE) + set(BUILD_MODULE_ApplicationFramework OFF CACHE BOOL "" FORCE) + set(BUILD_MODULE_Draw OFF CACHE BOOL "" FORCE) + + add_subdirectory(${OpenCASCADE_SOURCE_DIR}) + endif() +endif() + +set(OpenCASCADE_LIBS + TKernel + TKService + TKV3d + TKOpenGl + TKBRep + TKBool + TKFillet + TKGeomBase + TKGeomAlgo + TKG3d + TKG2d + TKTopAlgo + TKPrim + TKSTEP +) + diff --git a/cmake/external/onetbb.cmake b/cmake/external/onetbb.cmake new file mode 100644 index 0000000..2853837 --- /dev/null +++ b/cmake/external/onetbb.cmake @@ -0,0 +1,8 @@ + +CPMAddPackage( + NAME onetbb + GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git + GIT_TAG v2021.8.0 + EXCLUDE_FROM_ALL ON + OPTIONS "TBB_INSTALL ON" "TBB_TEST OFF" "TBB_BENCH OFF" "BUILD_SHARED_LIBS ON" +) diff --git a/cmake/external/openxlsx.cmake b/cmake/external/openxlsx.cmake new file mode 100644 index 0000000..107d45f --- /dev/null +++ b/cmake/external/openxlsx.cmake @@ -0,0 +1,8 @@ + +CPMAddPackage( + NAME OpenXLSX + GIT_REPOSITORY https://github.com/troldal/OpenXLSX.git + GIT_TAG b80da42d1454f361c29117095ebe1989437db390 + #EXCLUDE_FROM_ALL ON +) +set(OPENXLSX_LIBRARY_TYPE "STATIC") diff --git a/cmake/external/stb.cmake b/cmake/external/stb.cmake index cfb5182..5f7c052 100644 --- a/cmake/external/stb.cmake +++ b/cmake/external/stb.cmake @@ -1,49 +1,50 @@ -include(${CMAKE_SOURCE_DIR}/cmake/tools/CPM.cmake) - -CPMAddPackage( - NAME stb_external - GIT_REPOSITORY https://github.com/nothings/stb.git - GIT_TAG af1a5bc352164740c1cc1354942b1c6b72eacb8a - DOWNLOAD_ONLY TRUE -) - -if(stb_external_ADDED) - set(EXTERNAL_PROJECT_NAME stb) - - add_library(${EXTERNAL_PROJECT_NAME} INTERFACE) - add_library(stb::stb ALIAS ${EXTERNAL_PROJECT_NAME}) - - target_include_directories(${EXTERNAL_PROJECT_NAME} - INTERFACE - $ - ) - - set_target_properties(${EXTERNAL_PROJECT_NAME} - PROPERTIES - OUTPUT_NAME stb - ) - - include(GNUInstallDirs) - - install( - TARGETS ${EXTERNAL_PROJECT_NAME} - EXPORT ${EXTERNAL_PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${EXTERNAL_PROJECT_NAME} - ) - - install( - EXPORT ${EXTERNAL_PROJECT_NAME}Targets - FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake - NAMESPACE ${EXTERNAL_PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} - ) - - install( - DIRECTORY ${stb_external_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${EXTERNAL_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - ) -endif() + +CPMAddPackage( + NAME stb_external + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG af1a5bc352164740c1cc1354942b1c6b72eacb8a + DOWNLOAD_ONLY TRUE +) + +if(stb_external_ADDED) + set(EXTERNAL_PROJECT_NAME stb) + + add_library(${EXTERNAL_PROJECT_NAME} INTERFACE EXCLUDE_FROM_ALL) + add_library(stb::stb ALIAS ${EXTERNAL_PROJECT_NAME}) + + target_include_directories(${EXTERNAL_PROJECT_NAME} + INTERFACE + $ + ) + + set_target_properties(${EXTERNAL_PROJECT_NAME} + PROPERTIES + OUTPUT_NAME stb + ) + + include(GNUInstallDirs) + + install( + TARGETS ${EXTERNAL_PROJECT_NAME} + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + ) + + install( + EXPORT ${EXTERNAL_PROJECT_NAME}Targets + FILE ${EXTERNAL_PROJECT_NAME}Targets.cmake + NAMESPACE ${EXTERNAL_PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + ) + + install( + DIRECTORY ${stb_external_SOURCE_DIR}/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${EXTERNAL_PROJECT_NAME} + COMPONENT devel + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) +endif() diff --git a/cmake/hpr-macros.cmake b/cmake/hpr-macros.cmake new file mode 100644 index 0000000..f7da928 --- /dev/null +++ b/cmake/hpr-macros.cmake @@ -0,0 +1,137 @@ + +# args: +# provide: HPR_VERSION +macro(hpr_parse_version _file _regex) + file( + STRINGS "${_file}" _version_defines + REGEX "${_regex}" + ) + + set(_regex_match "${_regex} +([^ ]+)$") + foreach(_ver ${_version_defines}) + if(_ver MATCHES ${_regex_match}) + set(_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") + endif() + endforeach() + + if(_VERSION_PATCH MATCHES [[\.([a-zA-Z0-9]+)$]]) + set(_VERSION_TYPE "${CMAKE_MATCH_1}") + endif() + + string(REGEX MATCH "^[0-9]+" _VERSION_PATCH "${_VERSION_PATCH}") + set(HPR_VERSION "${_VERSION_MAJOR}.${_VERSION_MINOR}.${_VERSION_PATCH}") +endmacro() + +# args: ... +# provide : *_HEADERS *_HEADERS_INTERFACE +macro(hpr_collect_interface _project_name) + file(GLOB ${_project_name}_HEADERS ${ARGN}) + + foreach(_header_path ${${_project_name}_HEADERS}) + list(APPEND ${_project_name}_HEADERS_INTERFACE "$") + endforeach() +endmacro() + +# args: ... +# provide : *_SOURCES +macro(hpr_collect_sources _project_name) + file(GLOB ${_project_name}_SOURCES ${ARGN}) +endmacro() + +# Common installation +# args: +macro(hpr_install _project_name _project_source_dir) + if (HPR_INSTALL) + install( + TARGETS ${_project_name} + EXPORT ${_project_name}Targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_SKIP COMPONENT runtime + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hpr COMPONENT devel + ) + + if (BUILD_SHARED_LIBS) + install( + TARGETS ${target} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_ONLY COMPONENT devel + ) + endif() + + install( + EXPORT ${_project_name}Targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hpr COMPONENT devel + NAMESPACE hpr:: + ) + + install( + DIRECTORY ${_project_source_dir} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hpr COMPONENT devel + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + PATTERN "tests" EXCLUDE + ) + + install( + FILES ../${_project_name}.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hpr COMPONENT devel + ) + endif() +endmacro() + +# Common tests template +# args: +macro(hpr_tests _project_name _tests_dir) + if (HPR_TEST) + file(GLOB tests_cpp "${_tests_dir}/*.cpp") + + add_executable(${_project_name}-tests ${tests_cpp}) + + target_link_libraries( + ${_project_name}-tests + PUBLIC hpr::${_project_name} + PRIVATE GTest::gtest_main + ) + + gtest_add_tests(TARGET ${_project_name}-tests) + endif() +endmacro() + +# Collect modules +# args: - +macro(hpr_collect_modules_) + set_property(GLOBAL PROPERTY _HPR_MODULES "") +endmacro() + +# Add module library +# args: +macro(hpr_add_library _library_name _library_type) + set_property(GLOBAL APPEND PROPERTY _HPR_MODULES "${_library_name}") + add_library(${_library_name} ${_library_type}) + add_library(hpr::${_library_name} ALIAS ${_library_name}) +endmacro() + +# args: - +# provide: HPR_MODULES HPR_INTERFACE_LIBS HPR_PUBLIC_LIBS +macro(hpr_collect_modules) + get_property(_hpr_modules GLOBAL PROPERTY _HPR_MODULES) + + foreach(_module ${_hpr_modules}) + get_target_property(_module_type ${_module} TYPE) + + if(_module_type STREQUAL "INTERFACE_LIBRARY") + list(APPEND HPR_INTERFACE_LIBS "${PROJECT_NAME}::${_module}") + else() + list(APPEND HPR_PUBLIC_LIBS "${PROJECT_NAME}::${_module}") + endif() + + set(HPR_MODULES "${HPR_MODULES} ${_module}") + endforeach() +endmacro() + +# args: - +macro(hpr_print_summary) + set(_summary "hpr-${HPR_VERSION}:${HPR_MODULES}") + message(STATUS ${_summary}) +endmacro() diff --git a/cmake/hprConfig.cmake.in b/cmake/templates/Config.cmake.in similarity index 57% rename from cmake/hprConfig.cmake.in rename to cmake/templates/Config.cmake.in index 91ee8da..63b5fc7 100644 --- a/cmake/hprConfig.cmake.in +++ b/cmake/templates/Config.cmake.in @@ -1,7 +1,7 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -include("${CMAKE_CURRENT_LIST_DIR}/hprTargets.cmake") - +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") + check_required_components("@PROJECT_NAME@") \ No newline at end of file diff --git a/cmake/templates/cmake_uninstall.cmake.in b/cmake/templates/cmake_uninstall.cmake.in new file mode 100644 index 0000000..4ea57b1 --- /dev/null +++ b/cmake/templates/cmake_uninstall.cmake.in @@ -0,0 +1,29 @@ + +if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif() + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") + +foreach (file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + exec_program("@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval) + if (NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif() + elseif (IS_SYMLINK "$ENV{DESTDIR}${file}") + EXEC_PROGRAM("@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval) + if (NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing symlink \"$ENV{DESTDIR}${file}\"") + endif() + else() + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif() +endforeach() + diff --git a/cmake/toolchains/linux-gcc.cmake b/cmake/toolchains/linux-gcc.cmake new file mode 100644 index 0000000..aec949d --- /dev/null +++ b/cmake/toolchains/linux-gcc.cmake @@ -0,0 +1,10 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_ARCH x86_64) + +set(CMAKE_C_COMPILER /bin/gcc) +set(CMAKE_CXX_COMPILER /bin/g++) +set(CMAKE_MAKE_PROGRAM /bin/ninja) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchains/mingw-gcc.cmake b/cmake/toolchains/mingw-gcc.cmake new file mode 100644 index 0000000..a73d8d1 --- /dev/null +++ b/cmake/toolchains/mingw-gcc.cmake @@ -0,0 +1,12 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_ARCH x86_64) + +set(MINGW_PREFIX /mingw64) + +set(CMAKE_C_COMPILER ${MINGW_PREFIX}/bin/gcc) +set(CMAKE_CXX_COMPILER ${MINGW_PREFIX}/bin/g++) +set(CMAKE_MAKE_PROGRAM ${MINGW_PREFIX}/bin/ninja) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) \ No newline at end of file diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 6fe171c..b9a7477 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,23 +1,23 @@ - -find_package(Doxygen) - -if(NOT ${DOXYGEN_FOUND}) - message(STATUS "Doxygen is not found.") -endif() - -set(doc_api ${CMAKE_CURRENT_BINARY_DIR}/api/Doxyfile) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/api/Doxyfile.in ${doc_api} @ONLY) - -add_custom_target( - documentation - COMMAND ${DOXYGEN_EXECUTABLE} ${doc_api} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/api - COMMENT "Generating API documentation with Doxygen" - VERBATIM -) - -install( - DIRECTORY ${PROJECT_BINARY_DIR}/docs/api/html - DESTINATION share/doc/${CMAKE_PROJECT_NAME}/api + +find_package(Doxygen) + +if(NOT ${DOXYGEN_FOUND}) + message(STATUS "Doxygen is not found.") +endif() + +set(doc_api ${CMAKE_CURRENT_BINARY_DIR}/api/Doxyfile) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/api/Doxyfile.in ${doc_api} @ONLY) + +add_custom_target( + documentation + COMMAND ${DOXYGEN_EXECUTABLE} ${doc_api} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/api + COMMENT "Generating API documentation with Doxygen" + VERBATIM +) + +install( + DIRECTORY ${PROJECT_BINARY_DIR}/docs/api/html + DESTINATION share/doc/${CMAKE_PROJECT_NAME}/api ) \ No newline at end of file diff --git a/docs/api/Doxyfile.in b/docs/api/Doxyfile.in index 397d3d3..2ea97f2 100644 --- a/docs/api/Doxyfile.in +++ b/docs/api/Doxyfile.in @@ -1,389 +1,389 @@ -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "@CMAKE_PROJECT_NAME@" -PROJECT_NUMBER = @PROJECT_VERSION@ -PROJECT_BRIEF = -PROJECT_LOGO = -OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/api -CREATE_SUBDIRS = NO -ALLOW_UNICODE_NAMES = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = NO -STRIP_FROM_PATH = # @PROJECT_SOURCE_DIR@/include @PROJECT_SOURCE_DIR@ -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -JAVADOC_BANNER = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -PYTHON_DOCSTRING = YES -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 4 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -OPTIMIZE_FOR_FORTRAN = NO -OPTIMIZE_OUTPUT_VHDL = NO -OPTIMIZE_OUTPUT_SLICE = NO -EXTENSION_MAPPING = -MARKDOWN_SUPPORT = YES -TOC_INCLUDE_HEADINGS = 5 -AUTOLINK_SUPPORT = YES -BUILTIN_STL_SUPPORT = NO -CPP_CLI_SUPPORT = NO -SIP_SUPPORT = NO -IDL_PROPERTY_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -GROUP_NESTED_COMPOUNDS = NO -SUBGROUPING = YES -INLINE_GROUPED_CLASSES = NO -INLINE_SIMPLE_STRUCTS = NO -TYPEDEF_HIDES_STRUCT = NO -LOOKUP_CACHE_SIZE = 0 -NUM_PROC_THREADS = 1 -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO -EXTRACT_PRIV_VIRTUAL = NO -EXTRACT_PACKAGE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -RESOLVE_UNNAMED_PARAMS = YES -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = NO -HIDE_SCOPE_NAMES = NO -HIDE_COMPOUND_REFERENCE= NO -SHOW_HEADERFILE = YES -SHOW_INCLUDE_FILES = YES -SHOW_GROUPED_MEMB_INC = NO -FORCE_LOCAL_INCLUDES = NO -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_MEMBERS_CTORS_1ST = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -STRICT_PROTO_MATCHING = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_FILES = YES -SHOW_NAMESPACES = YES -FILE_VERSION_FILTER = -LAYOUT_FILE = -CITE_BIB_FILES = -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_IF_INCOMPLETE_DOC = YES -WARN_NO_PARAMDOC = NO -WARN_AS_ERROR = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = @PROJECT_SOURCE_DIR@/source -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.l \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f18 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.ice \ - *.org -RECURSIVE = YES -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXCLUDE_SYMBOLS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -FILTER_SOURCE_PATTERNS = -#USE_MDFILE_AS_MAINPAGE = -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = YES -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -SOURCE_TOOLTIPS = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = YES -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_EXTRA_STYLESHEET = -HTML_EXTRA_FILES = -HTML_COLORSTYLE_HUE = 220 -HTML_COLORSTYLE_SAT = 100 -HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO -HTML_DYNAMIC_MENUS = YES -HTML_DYNAMIC_SECTIONS = NO -HTML_INDEX_NUM_ENTRIES = 100 -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_FEEDURL = -DOCSET_BUNDLE_ID = org.doxygen.Project -DOCSET_PUBLISHER_ID = org.doxygen.Publisher -DOCSET_PUBLISHER_NAME = Publisher -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -CHM_INDEX_ENCODING = -BINARY_TOC = NO -TOC_EXPAND = NO -GENERATE_QHP = NO -QCH_FILE = -QHP_NAMESPACE = org.doxygen.Project -QHP_VIRTUAL_FOLDER = doc -QHP_CUST_FILTER_NAME = -QHP_CUST_FILTER_ATTRS = -QHP_SECT_FILTER_ATTRS = -QHG_LOCATION = -GENERATE_ECLIPSEHELP = NO -ECLIPSE_DOC_ID = org.doxygen.Project -DISABLE_INDEX = NO -GENERATE_TREEVIEW = YES -FULL_SIDEBAR = NO -ENUM_VALUES_PER_LINE = 4 -TREEVIEW_WIDTH = 250 -EXT_LINKS_IN_WINDOW = NO -OBFUSCATE_EMAILS = YES -HTML_FORMULA_FORMAT = png -FORMULA_FONTSIZE = 10 -FORMULA_TRANSPARENT = YES -FORMULA_MACROFILE = -USE_MATHJAX = NO -MATHJAX_VERSION = MathJax_2 -MATHJAX_FORMAT = HTML-CSS -MATHJAX_RELPATH = -MATHJAX_EXTENSIONS = -MATHJAX_CODEFILE = -SEARCHENGINE = YES -SERVER_BASED_SEARCH = NO -EXTERNAL_SEARCH = NO -SEARCHENGINE_URL = -SEARCHDATA_FILE = searchdata.xml -EXTERNAL_SEARCH_ID = -EXTRA_SEARCH_MAPPINGS = -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = NO -LATEX_OUTPUT = latex -LATEX_CMD_NAME = -MAKEINDEX_CMD_NAME = makeindex -LATEX_MAKEINDEX_CMD = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4 -EXTRA_PACKAGES = -LATEX_HEADER = -LATEX_FOOTER = -LATEX_EXTRA_STYLESHEET = -LATEX_EXTRA_FILES = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -LATEX_BIB_STYLE = plain -LATEX_TIMESTAMP = NO -LATEX_EMOJI_DIRECTORY = -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_SUBDIR = -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = NO -XML_OUTPUT = xml -XML_PROGRAMLISTING = YES -XML_NS_MEMB_FILE_SCOPE = NO -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- -GENERATE_DOCBOOK = NO -DOCBOOK_OUTPUT = docbook -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# Configuration options related to Sqlite3 output -#--------------------------------------------------------------------------- -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -EXTERNAL_PAGES = YES -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -DIA_PATH = -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -DOT_NUM_THREADS = 0 -DOT_FONTNAME = Helvetica -DOT_FONTSIZE = 10 -DOT_FONTPATH = -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -UML_LIMIT_NUM_FIELDS = 10 -DOT_UML_DETAILS = NO -DOT_WRAP_THRESHOLD = 17 -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DIR_GRAPH_MAX_DEPTH = 1 -DOT_IMAGE_FORMAT = png -INTERACTIVE_SVG = NO -DOT_PATH = -DOTFILE_DIRS = -MSCFILE_DIRS = -DIAFILE_DIRS = -PLANTUML_JAR_PATH = -PLANTUML_CFG_FILE = -PLANTUML_INCLUDE_PATH = -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = NO -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "@CMAKE_PROJECT_NAME@" +PROJECT_NUMBER = @PROJECT_VERSION@ +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/api +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = # @PROJECT_SOURCE_DIR@/include @PROJECT_SOURCE_DIR@ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +PYTHON_DOCSTRING = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +NUM_PROC_THREADS = 1 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +RESOLVE_UNNAMED_PARAMS = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_HEADERFILE = YES +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_IF_INCOMPLETE_DOC = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @PROJECT_SOURCE_DIR@/source +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice \ + *.org +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +#USE_MDFILE_AS_MAINPAGE = +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_FEEDURL = +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = YES +FULL_SIDEBAR = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +OBFUSCATE_EMAILS = YES +HTML_FORMULA_FORMAT = png +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = +USE_MATHJAX = NO +MATHJAX_VERSION = MathJax_2 +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +LATEX_EMOJI_DIRECTORY = +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +DOT_UML_DETAILS = NO +DOT_WRAP_THRESHOLD = 17 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DIR_GRAPH_MAX_DEPTH = 1 +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/source/applications/CMakeLists.txt b/source/applications/CMakeLists.txt index 07465bc..0d81988 100644 --- a/source/applications/CMakeLists.txt +++ b/source/applications/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(periodic) \ No newline at end of file +#add_subdirectory(periodic) +add_subdirectory(fes) \ No newline at end of file diff --git a/source/applications/fes/CMakeLists.txt b/source/applications/fes/CMakeLists.txt new file mode 100644 index 0000000..ffd6f2d --- /dev/null +++ b/source/applications/fes/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required (VERSION 3.16) + +include(${CMAKE_SOURCE_DIR}/cmake/external/imgui.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external/implot.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external/openxlsx.cmake) + +project( + fes + VERSION 0.1.0 + LANGUAGES CXX +) + +# Compiler options +set(CMAKE_CXX_STANDARD 20) + +add_executable(${PROJECT_NAME} + fes.cpp + ) + +target_link_libraries(${PROJECT_NAME} + hpr::gpu + imgui::imgui + implot::implot + OpenXLSX::OpenXLSX + ) + +target_link_libraries(${PROJECT_NAME} -static gcc stdc++ winpthread -dynamic) +#message(STATUS "${OpenXLSX_DIR}") +#target_include_directories(${PROJECT_NAME} PUBLIC ${OpenXLSX_DIR}) \ No newline at end of file diff --git a/source/applications/fes/fes.cpp b/source/applications/fes/fes.cpp new file mode 100644 index 0000000..da91e29 --- /dev/null +++ b/source/applications/fes/fes.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + OpenXLSX::XLDocument doc; + doc.open("fes.xlsx"); + auto wb = doc.workbook(); + auto wks = wb.worksheet("Капилляриметрия"); + std::cout << wb.sheetCount() << std::endl; + auto rng = wks.range(OpenXLSX::XLCellReference("B7"), OpenXLSX::XLCellReference("B52")); + //for (auto cell : rng) + // std::cout << cell.value() << std::endl; + //xlnt::workbook wb; + //wb.load("fes.xlsx"); + //auto ws = wb.active_sheet(); + + + //std::clog << wb.sheet_count() << std::endl; + auto window = hpr::gpu::Window{50, 50, 1000, 800, "FES", hpr::gpu::Window::Windowed, nullptr, nullptr}; + + window.keyClickCallback([](hpr::gpu::Window* window, int key, int scancode, int action, int mods) + { + if (key == GLFW_KEY_ESCAPE) + window->state(hpr::gpu::Window::Closed); + }); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(window.instance(), true); + ImGui_ImplOpenGL3_Init("#version 430"); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + while (window.state() != hpr::gpu::Window::Closed) + { + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + static int selected = 0; + + if (ImGui::Begin("Table")) + { + if (ImGui::BeginTable("##table", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_BordersOuterV)) + { + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Core sample", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Depth", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Lithotype", ImGuiTableColumnFlags_NoHide); + ImGui::TableHeadersRow(); + const int start = 7;//54; + const int end = 11;//113; + int n = start; + + auto sample_range = wks.range(OpenXLSX::XLCellReference("B" + std::to_string(7)), OpenXLSX::XLCellReference("B" + std::to_string(11))); + hpr::darray sample {sample_range.begin(), sample_range.end()}; + //auto depth_range = wks.range(OpenXLSX::XLCellReference("H" + std::to_string(start)), OpenXLSX::XLCellReference("H" + std::to_string(end))); + //hpr::darray depth {sample_range.begin(), sample_range.end()}; + //auto lithotype_range = wks.range(OpenXLSX::XLCellReference("K" + std::to_string(start)), OpenXLSX::XLCellReference("K" + std::to_string(end))); + //hpr::darray lithotype {sample_range.begin(), sample_range.end()}; + + for ( ; n < end; ++n) + { + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextDisabled("#%d", n); + + ImGui::TableNextColumn(); + //float value = static_cast(cell.value()); + if (ImGui::Selectable(sample[n - start].value().get().c_str())) + { + selected = n; + } + + ImGui::TableNextColumn(); + //ImGui::Text(depth[n - start].value().get().c_str()); + ImGui::TableNextColumn(); + //ImGui::Text(lithotype[n - start].value().get().c_str()); + ++n; + } + + ImGui::EndTable(); + } + ImGui::End(); + } + + if (ImGui::Begin("Capillarimetry")) + { + hpr::darray P_c { 0.025, 0.05, 0.1, 0.3, 0.5, 0.7, 1.0}; + hpr::darray data; + + if (ImGui::BeginTabBar("Graphs", ImGuiTabBarFlags_None)) + { + const hpr::darray vars {"K_w", "P_n"}; + std::map> cols { + {"K_w", hpr::darray{"S", "V", "Y", "AB", "AE", "AH", "AK"}}, + {"P_n", hpr::darray{"U", "X", "AA", "AD", "AG", "AJ", "AM"}} + }; + + for (const auto& var : vars) + if (ImGui::BeginTabItem(var.data())) + { + //ImPlot::SetNextAxesToFit(); + if (ImPlot::BeginPlot(var.data(), ImVec2(-1, -1) )) + { + ImPlot::SetupAxes("P_c",var.data()); + + for (const auto& col : cols[var]) + data.push(wks.cell(col + std::to_string(selected)).value().get()); + + ImPlot::PlotLine(var.data(), P_c.data(), data.data(), data.size()); + + ImPlot::EndPlot(); + } + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::End(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + window.swapBuffers(); + window.pollEvents(); + } + + ImPlot::DestroyContext(); + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + return 0; +} \ No newline at end of file diff --git a/source/applications/fes/fes.xlsx b/source/applications/fes/fes.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..64c0920e827298a0427db04a276fc9e32d275b1c GIT binary patch literal 465904 zcmeFYWmp{Dwk?didvJFrL4p%3!9#F&0t654?iSo#0t9yt?hXka+#9z>Zb9CC&c5Hb z@AI7h_ws{uH&wM}%{k^6W3H|!%0R>5K)^vDKtMoHK&ZX_EhGp90r7+h0f7yH0HyWL z#@f-u+EMS5tF4KH4y(%-EAm_zDEe#&;GqBa^M7*&Mv`@G7T9ruKy*jK;_VqBIg8EK zloz>+tPLNI@|T;heARDc>OcQk9k+v8z@VXys$Ie$=L4OS_MZ-(-a9c#T%pX_l_R|# zE~1B)c(Y-llC2lLO23O0tAz zOxLhr3Pl$_LLw5IH-^2jc-z+Zi3xp3!K1(z@4TXtn&?lB&Ekpk(Pk)9{POI)N--3s z5lm4JeBO;l71e+^TT(jIv?Gl0$nc+ynz zEMD;0Fl!E^pRNwz@4#LQVl`H(jnu|MKI{s<_MKgWdz`aA?P7KrQ za0}irU^jK?3iBiRwI|Us*Z|Mprg&{ugunB6c;UB{m7yEZQWc$5(Y37=%$KQq;vVU& ztr8sy_Z_f9w)wxAWB&Wz%i`o@pgAytKz>gYBW?VPjFeMWY!bV2#t)DjDG&NQ zDKYuY57&~Sjyd(DZs`@ynXW09rqy{ac^K|aScwvde(9#uEvi3d9vU0rF~8YMMjlH4 z^rf`=eo%VJ70I}j^XpP5tZY3ufR*OShv2J}YOkMQllEs`^#pqY&5IA{-)XcHwn&0j z2^B|Q_l`*;@H;@~3f;8cwsP~xReTbz3$mR2_)(H3Fcmsu`}U9*wzf52!U5ZRDMiv_hrN{(y-3}fZ7}zm zqe{?dOl_P8YiyJFl7kUmXGdf zY0klWO5<&C5$$IB^_0;*d|mukjRU_~yTh3C*vQ$kD1kJ5GA(r!V|C`VF`|_2;WzUN z%2k`@5APGPf+)#g?E;w2Fy%K}n8>_qJqV^BoQOd(+2kJ>?USl9%fE-t_fmYYw?mhz zoC^IMLV&WDnR-Dl={41x9O!Fdt|^L&8M+w@m&Te)b?g`td{8xyZr3CEfgTM5`sRJo zMOk)s17Q#to`0_aSqd z4zi3;b&`Hye<*&82nP-WY|+UciP2rhYba93adBOs^1Wp)(zj=d)@ykH40vWZQ5G*X6k!GdQ>Or&Hi9qjY(AYeRC(5WY%MhtgpUdg$6$pCzs5U55GcI zWzGi>pAqnN%FX;3a5eCwuvOiB<#CBAgzpt*ZhBJk%I7mIT=t$?*i_jcVdU>8%H%z( zow4VCu<60MAETGh8H~cA_r6ykJD7>HiLAXr&@~ZXn@j#c;DF_Q=CHOi&lN0UUWj$e z7ivbuZ6=TLS)j{!>_`|=HU`>u?Dm9VAX79bU5r)4=H_)??~r-bcI|za_h!dw(pqwO z1d6!6o4qE#Y3nJncg}9=RY?z<=$Xrn@y(aH1P!im2Y4idN6GB?8$@x+gO7p-(^KBz zx?Npn-8P+&Mfw-GA}#{=@O-S*<#7;c`I93#uq1_0YD=NiDfvl|Tq{<-F`x%<7ZWiA9 zldGz^=95nKA-h@%<3$qnw(mN)*9vCRc&ffx|#V3;toEA zU#cFTWM{QVUZ9x^{kCd!7NUrqL6qk7J$6T#rQ{HX zfW9T7mPQcUk}S-JXlskvTvcw|^4JEPnor#EaVb3UsTw?ls#Quhyotp<>tD^iLwG*A zi7tFID-!uF5&wqa?CMWdb0|MrjJU1nlCTdK@?6!2s6_aX!qS$olB74ic1VSWv&*e2 zGY54+Te|+Z+s@XvVhklEuO-XS)mj>7r>j^rodx(a4kKv$%CeeFjz*i=jhP?3i5fcZ zAlMpzFYDSG9j&-Icy_f}Mw(jo%RPAI)#aIoaBs!D4gE9J@gHe(PIgbi9&j2?NDvT& z5D1U}ZT>YI|4yF&G9Hk?m;)U9fA>#oqMT(nJ4z79{~>q{yq>>GBIo{#;G+3!WvSf^ z_3JACa_fVRtUdKqu}OROb{J~xy+JTzsA?LhFiNQF*N4)G0=u7-iXV3p7&))n-Cfly z2XYmgitGF$GCHeA?;fo$k?KU8zp<3gN&X0{e#L~jC^c5joP)_d$3pL1Jzt2qE2ey+ zI^a+56CG#VC)WX|r8gT<@7g!Q?fl?eH23wfqkaX%ggCBAN~GwL2m3U&tuF)NC=tj2 z!8KRFES$w`C^d5PCE4-tTi<$#lf4st2gmQ#Qo=KHB_6V4JN^LPZJk6lgwf=mr{DV^ zY^0N2*F;mkNk)21WxHpr8BXzS?pS^hb4f5BsEwc}u82cP#>1k`xP1-4tX9Q0I4R(i zaTFrYbQ7Z}r+}g^1+Fk9Zo#D>HaAhBpm=6WX1YKcseudTL81sTkPA_kD@Q-k*B~)p zNw=V?fIX%o$F9`SJ9Mw;$^2%vzVQ*j+W+4h2RJQfY$HQJC>q=-Dt~JZW4F#5 zJ?=i{&e^sQ{?py`gM&sP0+`}V{k=)w@YgWXB ziy)2-$ebC51p;L`KapuvY;kb4A&tOb0aj0Exh8z`#|YzN8YLJdf<9mJR`E9mxnl+c z0z2``ADVH?UcV8q@*@b*vvDcVO3mI`gHcjg{4P~c3{kZX%L5NDk(vDs{FMP$12@|} z8a}|y9kv7E@xh6R=gESut0YcyyAR%8vHxE4k(U5Iz$t(JZv5w(X!b9z23UdI+~~l7 zU=pUcKkvu_dPznKW#sAwWF{*r`P|qI-I3fW>WjW@@TAIRC~RABmk*?}B)gVv4-GeI z4DreOs420h!7YD}@TaiC66ig{(b*W}BwV1e?sXn>y4zfEkNwC@UsWL2dA@w4b}+x5 zH5hn#3(YrBUPbF(tLjIxG^ z#h*h&>kf*Ex5l789XZmwZt6p$@VRY$Wzya?gyvcDj^E2FHAX(P{XSI&YrBRBR)5IZ zxnnjB`?3u>f3f#Hk`vPA$;|jz<4aG88VYy)Y0OH`!n{G@hl|irU3AMxnX&WtsT%Gt zFwj5o)X`alD7RxRbR^}QgWCuX_g~!q6+TC2qb~_c8%=GQgTV*sKW=ECEbADpKkm-3 z*~AZeHEA#2zrY+o^3zk<;$GCEBo_RM+N<4ZI7tCYiC8KFX7Y8`VNJAQjfy-6W(_O{x)-cH5>iE%8U17X`^>L@IRj}heC%2w#^kMtrW~t#! zW_6?}@^dNPOaafkAwba{Bf?`m)({v+`M%1W z5i@Q`7f(56R`^&~Zdt&4dzH)ElWXICFO<(xb97evpb2Xo8PV}wX;U1|`!EUR(3nqG z)yaOF+DDz*M^0oJi__&cd((XjB~qh%roU;2xk(Bhwdo2(yR}J7E4r-+H@nD#;coFs z2kvlX4Yr|==A-KI_A9&7Hzot&)i1_wq$K0-2STP820q?)Wd0QN)UOZNyBMXxO`R(( zru8u6V^&@Wp-k0)NaL0FjzaAHN03~^ZS5A7r}_lgqjRPZr9GQa#)IGty5wno*`p~Ez?=OzF8b+5J zlI9iCe)09eztPc~vB?@8{3;}Fk(70&L2GW`R@K8tt8Db0pY-zxw|M<@EI%`3%>nke z3bu|gyR9*ftma%ep;qKE4u@@LUm6Kf{Ll{M4He6VbWJADZblHzn>fw)BouMi`&%s( zAn+~?==ZUm?hjU18*7dD(!4^FyGkRi{C;TW#0enETOlAykRuA`>gADgdw5ycSD%4v zj{aFCg|-6tbJ4?DH5=lwkcY@bmb2uj0fVgC!#khzOOv>MPw(J&Kd+~KX^cg8lFJSF zbZPd!+o%_m{sWMyI`0kRKPERE_m8*3=fdN9;14KeEr;9HGOGh|y;hq$Tl;m)b4uM; zEZp$U3$JFl1QRkhoSfG}56g-6l2%Na4r(AW_olaGQsny?xO7&%IHtdLUdkpR@*hrG z1d+IGr0>vQXLdo2mq3#aJ}IR=xohnqyHsbKn(3=eU9nBCsavQKEY zOE`@qd|#d)g)!*z4i{cxD9uSvGJdO%%o{$)6r7~L_wXtid1QLK7^(AndzD$~*0|j; zm*4L@#$A^nltUxiiB-8^Po8%Zz6k0l%#ev=B$Yd8#qX@%I(K@kEiyGyDfh1w4BtBw z5*S&RPWzE=SdWt``AFehRV9Pie+@QM^LPlKW`*GrxC*XrpirrColo2=M&)zB*G?9-@7jmko>EHjJ)8MPy5h!EaID3WZBKZ=) zxXzf%t4d;Z$TApJ-<>&!rH>?*BHGAhd6M&Zb0;=BS~>`N73HtzW40mBePKJr^f4<5 z)S;MXuPQxAtACs0x{Qwz-4gRWu7kYaZ}wdk>)N&Hslzcv;AW%KR3fRPmO;)z9TZP` z><)JwNDF(sLxUvMX1x; zj^@$LIP~G{cg!}_xzBAUls-k}-?Tvu`)qY0>HqrzDu>9`JY~7edzoPGQBp7J^2Zd| zWcU$yJP2f8iB3a(#7Z1>IA(ZN2qs^ZP78h1N<4L`$RCD!<*f#|CT-_d(hk0^ogVs- zl^E(U%y5YiGQK9A_WH1uSn9B4aB~orzF%i{8L#RZh*XtSL1RI$@&6|mk81`lu5|D} zytqjsaB)0~|Ix+E44=rBmMg>F@s>J5{!cHU|2+ib^(8;L(rV>v587OL}^$U@R7CfEW`lRe+>JZE@c#z?~ zG@T#y{{<|_Qr|b7n))Pfn!_4qtz{KkApa*7Z7p}2e&Ql8olf^{>J-oys)SHSV1~kj zmhma*cY?PXr@K&!h%j5e*svn_a0E zmvHMRow|wmN8`cj!uMJ2yAq4}-OHO`r$<3KH>znxc^#gxn)Yk%Z@TnJd+2yUtSM!b zWmtRn8~mL*11C_G2n4!NiNxOUOAhxCE^B8@ongy7J#iZqJ~rB3r0YRi9Z=eK9;l7m zE@;UPw?v(&{S7{gW>M$h$6-fB)T4yY3tl)RI)xp!dU)2v61r;fqioQ>=~`^naQS zc%~OB>Tw_-)BW1Yiir`fo_eZNlC(#Yi|W0?n*7`HI&MbGcNmk&K} zEpOyxiN8RI68a(JrG6Z_+zi)=OI_l@#O950;-(KYj#;38At`m3qnm{F=qN>4)6Jav5e1}Oa9YK)?jPOh+~YZ#1D5H z5C!7l>n78L!}CZ7Le$Ev16}Li*1@)kuO3(FKQols6<%S+{Z2mra*JHAudOZJaJd@z zzHsEHd=Rw9y8@65!ys$f1?ts_{I5eOM=3r*~h8b>NuC5 zIgExmtb0ZIH>v&c<%#~jULSt7l=2~ZX%p3;BC{1^Q~mUX!b}(CMdQCg`9Ogvq4804)P1WX?Eya~t#9M&VC4_}h}^eAzSz6cI8jr~ih)WYotfsL zYRD3AgyS7Q4C<=3Gkhl9(%{b6a>(cPMq!nyxyN}6H>x1YyFwPfMnanZa}$SK;gPOB zTl`{-=)rP?{*{xF^gY>&b$1v~B)%&6UNmt1G_TfBB&)dD`;A%SU8sw$t_tY-_bY&_m&~{71 z<2l&>^w3ULc)Gmk-Qj(Gbqz8YxBPWFx(EuEciWXf_iWtJQ@Ae<9$Q6^X?Fu{-1Q*18EPOYGcS${HXIKL{WD>Tjp7r|4BP;8=k(LfFqH&E4q4Vpl&l$hR zMReNPSZr&+bj_nRylv9k!KI^p@AKe}KgcdR;{)1ZL}QTi+fHnmQUORRnF1eYOZ3G?@`<_p_-}suiroz``kMvec+S|9wCm? z7_<1Mp_OJ#KyHj7!b#6&yziu4Q#0`HF5u$4k{Mwi6a10xw@l|(RESq&dB=6qh2!s1 z98=%%wzp-@nHr7C@nTUp5{L@KlY3zJP+Tx05krJZ;C-Ny%tz)liAo;VVS!4w?Qh22 zg2BO$kB+n+o^r^;)^H%2N!5>3R1CpTjLFX4hj8?(F?S@O_8N2DZ6RR=!KOGZ`%+~{ z%w(I6k%ZYpg&i!yeiHa~xtbIf$>9H{L9`rkAsd4tn_N_M7BYP>R5Dm}eMeg)66(8Z zDwtT6I1x5|;?!E=&7U6J8~^n7^7mTGEnZ)&9Sj4U=*1D}q!s1ALL5(uHqMKLui@E{eF2bNwIy@Q?MXqWKR^5YgQAN`^j@!#>Ldmoo{8pJRUh@dV< zJlY!+TBFCSg+tIs#5Cll`>anrc4^Ht!(HRcJS;51^&-FqB0$0-B7H~eboq)G>CY*R zXPh1Ik%dcvn!QJt1)iQ_y1bYsKJlx~o-AAZoenP%X#Ub3dU|20uLq$w0HF&8F)S3) zw*Qc;j1peEu}6UqJ&)^f_U-x6d$(yqNcrSNAl{#Y4y2^XQdU~%p1Xq50ftCR@WUq$ zX}QVSn9fHv(zfK(sBx}*j!xfXuRX>M3U?GeD7!YB&~7K?ZhFiXop;}Dh*HKY>kFy} z(bAdcgUPYrfR(V$ZFjGv9Yj+F4krn#5gh5>Vo31)*>uG~AEB(53lVappcdnR6$&sh zH}sR##H8%`uQ>Cg+7LDDZGJ8cLQSNYOXBnf#Fmf!iRN|e@>V0*|EQh)uYAUWIFXCz{{)EGtd9`RA@TTE?pf!vGLdig6 zw&VgPKcIAd5G6PIkF(rXkms~QXcQqk~N{yg|2l! z;Y%VE4MbPM920_wEo5ccqv740@r?)Tv1}AeWkqsvm2j}7GjTE5GS8yQrdaSLV<&B^ zVJvNwC4F%r5!=Qnx!+_;_+m+yRVljtOxp4jca0t1%ariJ9u3J&w>=vPaZzorn7{qW zU{5HYm_5pHLP_5B=ac1TzBUdo3Rd{Y#?okGk#@}Ic|p4rOH2p}o?LpmY_GA%U-L|! zkPM%|b+T1Vkv$*(;UyU87q zVKsft6QmkXRbsL_uV9tvB6xHxhY|m#fpjU8T1IwNDotbBw_?azB|{2 zugR~qW9Wq!w*;0cEfl5huqEC=Zrx&^UJd13!38KA3#BuQrYToyKw+hms#tDPy;I6Y zz_Mgc-h3s1b<~_mmGxYU(3e_-x?~E?^!eC^r3ZhbF?T@XmJ#vm4&8>;v(1)<7c)!< zoatHixM6Rx?&kx}!Z`xYin(iA#_F!;HyAx~$9X_*lY(ubA@8Dq1^Y##O5{URR1FCn zwTMcKTF_;Cg!}QE**r(5fm0RSE}+58eepgCi3Le!;`pPl1SBFFy;YIV0MOM0)T$aH zqflY|w40=LaM@CmdGy~A>BsY2I!}n|<&Z3`oq-MCg#xH=P*}tt>-sWT14A6isZ~>o zl*rCFza6JkB4p>vytIJKDZ=+JX@&@2kU|wd`N|o*(qz(wn<&CSL<{VpK6l)8d+R+i zL(+sgNa+){0(6^V6PR}Wi}Wy4wbq87xxBjH)eU|!cFfuZ<6&AhKLr*sQm6{vr|L`M z6g;&Oukj$&>33}r4*Ha7QQs5+Htc70%)Wgppjx~v7mR77`W8tWB`jG$F2c= zN)10~jFDno;86z=lWU;#3HxKF!D*+%o!w$3Kq3s9gdZ4P+Mt0{*uLIX4|%Cnf%0j+ zqiEs17rb6iRS43o^y--^Kncf*VJ_XPjYk^l4n(**s1mp?crk0)KQob2@t(;<-6S*9 z@!eZazO+GRsN!8s0EG!Vnis(naKsH?kS(qAa?P@fp%#Ixf0{s;K4PXM0n)L(_v`4E z6OEIlPg1adEp=EPg0GoC;w#0Db#+KqD6?=(6$gLqHh0rRpFUcXgG3s0aV&RGh+{T2 z^`4nmH>>nF{Bcf#X4<~S4WaPdMB6jjh+p{Hrs1%(I3D%epyVj%_GN&F&gTFgqIJPJ z+eIP_#FE9DOio@))~i{E0*j^SbX1TbpBHg!3mYv>j?;%BbIp&dJGom66Th;o5XtTe zx6S4d%YpTRqvp*sERXH`mSLmEED@(}?D{SiJt&qwIy9J&XdA4?4{1a>8kyT@dp;5g zf2Vo$1%dkp3yoWnzP@vjz!DLyUM|v%-@+kd@k8$0!epA0EqeK zaf^#SahVPbBy4miB%rFehb;tDRM3#Gv&)(wVBtdFoTb^`d_^+dv+rkp8uaL1_T@NZ z_*GskYdVagsHYo&uUE0NOoGZ;v%MYgjxyjC74IJ(7!nvv5m4xDpp;?Y*0RK7z#8o5 zvyAX*=Wvnv+zg;IWHU1`h3CdQwCdil0L7tk!Idh&X?zv4ruhBMlvA!`w5VaMYx0FJ zBBu$LKRN|VF5Ilclf;V}Ngi&=*C~)MBMJqe`6lX1lw?D(0pL@tlWdm%w8kOLTn$6Q zhc8}eWnz0~1`sw=9zfUvOjIjFe6y^+K7Po5d<+BpkkV14ZAE4Y_xOI_I#qmx3zdl4 zXivM*MovMqK-Yv3nQWpTLM+Z&^p5quGI7}TXZd^9m7a(nbl4c2(~vsU!3+{WFLuN& zkn`6+W!K4cS)lTFb$~L=jJx7b*E<80l!lW5O@nLI=>rptGlH6mPdNoh`4+Fd&iIb{ zz-Ft5J|?+DKOiXi+WqUk8msk29Dz}R;`HA;s{mE#zeCA3$-#;jGmJCxO4;n(d&@OA z&I(w+V)KvBozVil0r_c>wHK!u2P>x-D(NTZYo)HfHPcrK**I9G12~sIUsng;SzxY- zf6FbVNnbj`N4H&Ygvcx!@aKIGvs&+I5Wt*Ine-Fu#)cz^5F6WAG6Ths#GWL~C3@u3A~XR)W_i<_TcP&>Vo1bD(ze!0X^Wy`QIeF>Wp?qavF{RFe+6b( zL;Cc{;Yl!vY1UhX(O`~`=qa@DV;LKd%1L5TL=bdE!5|JHKoWyKQ3+D34g@&1yqelX zJ=EUSgrT2|1+q=!>@TsDfOcuG$1V{Bs7)C;uCo4n5kLRW${QLuyck&F%=xDB!?A;> zQOvotbxEr$?J{^Dg_}Co6)Ua-aRd>^&;bRPV?3b-1VF_ypM?pKD4?#xtj-iFOrz8G zC?1Kh+2-$YW?|x6S|~Hci1#nseZq*JwZF8wM$e2X&=25ou=obhq2R7-Y_^FFs4S zUx3;wJl9r0Vcpl+H%U;gHwcLWR|Aa>&>Ll621rEtECB2=xUgseJ(S1{kKb2eQ_ceX z6kMNi^d7)b{LcFx1y}Ss8VVrlU7avYKyqXN@&lkwSnc$9r(#F&+|Yw~G{!@0J&46B zNFpzZq@RxsX^11)pKXr`bCLk$w?zgv3h*cSja5)lWzx3v)T(^){`aQn*n2il8Zgq6 z-vD`I82=VRCP`Ayf@o=7k#DYPo`l3L4}cvh6Bbnivjnh0t@i6SnUqS$AMKOQJP{vt zf0hG{rLDJ4a9k>A1;(0NqV3JV{=j(htiOTfxEDqen_^+Y?n8|I_pFJ@*dsG`8(XuK z6Ggl7I9nkWPCT+bY1+EumW$YPOX4(`eqorkbmXE+96pZ$&mq!kD4rs+Veu`-G6U&Q z#F-69K0YQLfCj&a)iO6$@OYL6A3?b2-TTGiR z(7g_+xaieOe20sblMU4YbQQmPxst9n4&t-1`}y9>3N5rX$2I{<5zt+GsG&C=5ar1kqGf9y8uPoO4d7m)fkGGa;B=C>VpUQ={0khz2E*0w-WQX z6!$$47rK|@Yv69wMEtmJ46tEQkumo~q|2&f;!?;tO}e41%V8mK9>Q`{ea`N~ZgwQM zzjOc!pDotm$7f8cE@LY~;q!be1k@fAUXV0e^f3(n>(CMaW(o10G5ZP+eW|eipXgeE zWz>VV*VHUGjdEV57gfHq*<0i(Ue$652<+vs+(iAer7(E*FD#4vQEI8Viw+0~y*N^rGgO;4f`w-+XE=Ti^*&d!%SAhJAN^H1!-5pI)A zq!R-jDfW1=VdNB2-tip}QOtKqH_Q-%p`;Q@VKIqh`4dTR*Y{Qc;}H!Qk9caaW-Kac zM68RI03Sg?qy!(Stx%dM99K~^Cn`%g3O8&C3aX2%VX`b?Z_nF{%V{w1C4`a zRm`$H@LWcq9?coFq9i9pS>crG)wG=U5hB#r#gHGN+6vMFf=Bs3VVllxjWdk0?msMj zeD5jzJ=&J2=jT3R*gZZ|Vc$=U4Oi6P!tET5?@!CSzJw*g4dZyfY2-qDYU}c}c+x+K zS(ozf$EiS39R$BQc4SmDN?50hENW4>ng^a5o1aSCm_yIoFG+dp;o$<;+kTX&A=Wzm zrudp1>qq$hz-&*=RCX$cF{bkG8c^USo{D4a&3eb+PvqjxOgOvsZhZ{iy4u>K6H~;ZDh-g*WmaQ^K2xW1U|I>32T+e%*`JT&Bw1albZIB(^*&x32 zS?@VRXIN1`eW(C^_3E$7hkIMxyj*RW$23jc9tIsjdOgt;ZL#u_Dt&y=KX$;Jelfhk zEg5FjZ}a5S!y?M=u52F9PP@~D1--mn-5xKNmrt(jueVR9yU)Qz3f?!z#~qL7!uN$s zZLY3uPZy7m-RDL9pkpsrk0x*$=xM|I=4yOA>*?lfV`Jlhh~2~O_V~Deem<++_4;`0 zPk(>(HJeSAx7&Sy_j&jEPV{xfO;*;^$?;avc=R>s%S1mqsBhTB^ZN90Z+f}wWOPT1 zH`Dv(&)%O?P!^fjJy`rSPtJ6Pt&C%DulIIE?t}c%^*z{gc z7k~CH@u}L}&rkoPtv>#lKHmYadUt+(GD!xJt=w;MJ=*jb3I&Mu8MPwfP{n}_exmFOZtAr2lX~d#>t6R&kpWa9GvqA z3%5NTAA9SLJ>A23qg{5eb~Ke;c5Dhi9UOygSmYnicX$x``n_A9Lf+iWKY=TrK&y|} z9S>Q5j+d`j+r7mgX2QMBZ%>0k$B#}o+YKFBL+Vcnk@q$V(cWH|_uH4ZMXQ33CwI=# zWI}8G9C=Tt_xGn+q{j#QtO)K;Sx@Tk@PyBMh3|_9_d8miwr+Rkhdk~V-OzzAAaA2D zv+nWcol2&??oVzM^G-(vA8)2NZm&lhBOk#W)3<3I%LlJlZv}80_@4Zic^eWFCv41&9j;HqtHSL9 zYusHUE9*O_&O!p-oq9e`UgxJJDgJr7AH`<4dc?A% zLpbYU-VEYP?eC58KF$YVf85*qrlFs5jg9{^K*{O08G5&0P(pV7;j%~J3_Lf)P}JLu zc$4hBTC9>j7som=$!LxHhI4i-2Mr5(o?1TKu^F3}DtEaubZ{qCu#HWq7D1VPZWt@a z-wcPI$hAUTqEbf$Uq(TE`TF77#?*?(3YKp4&WSXs{DOJ>uKk0zS|n_lgoCDgU_K# z66I}vTK_3t@uI-3WMfZMM@1wA|5_zHIR+z^P5zSVA*}rq*iQGG=B_SPdn39=kH`hn z9p{~e3XW>9$ywd^0oS*bHPRYmeR4OtniIliBA0a4e=kK6AgxBG zxyQ;8i+AMO9Fw8YY){V^Fr}6NVKV84mD1OtzM%j+#ItvrFj~8xx z+XihhWiX4g^p=!-7^nyc#=e$L6cjZ?d8IU&9H_7|$T1cmr?8cpa+f~BrayXo<0b06~D2^4#; zWTl)rvZasGqK*wa^PQUAL5}&?>;&^sc00`~1apse&RguslaG7rScdF557WK4J^Etc zqHyGpu7E2A{CRZ$)5|p0Oj=ahTJL6;)Yk+Pz@dASz%oVQJ5v6yj#}gGyMQZbFcIO|S&=MsOniTJ(TisOBvxw5o(F6nEqwekI*2>@7^vHqy-G-u z!Fs_d6L<=C%=xC#;|b&OBnC2I=c1cOdo4jz>#d>9hmxtR2_^Wt`=b^C8rZzDT+^Ej zwOCseCG(SSwP3Nf2nuHpxTOehUn(&4QcF;d71zxNUb9(M;hRh#D?G{dhoc8xeE9_t z%7-`Rau@gJKn&s60Y2OfEXYvkjx0ZP=Dp^o;8+S_>rFXUKnrrjTESN~wHCk~>6y=> znAUbolqOhMVlC+HnxMb|?}iB?R*M4V3qMh<%PzNfOt=%sj(oN~qQ`x!Y%^Lk^VqUc z&yzgqb-@!SV=)hT#AcQ72B_7~dJTWXBR2no;I5H7*Hq@IV4~d;Vk9j)RN$J@WItX< z1k`r8fDlX*7u!j~oTG_PT5AX3SP@!H-j`al#fnbjE z#0T*RUNpQ5nzohM;fagCV3V%j5?Ynw?PhgUZq4aygZq)wY*72^$UeUl1yMCuXUHD`7lOXT9U8^>7Sfd0Ku z#?oNKx7QVLwcfec?8(X4(Xev%?B-V+K@Fq@DVzE+#He929w&=-h#WvhL!|;x07#x~ zBgwMY&4-`Pp!0FfcweE(M#z7@iv*VW(xE@gdbF5!LI=7bK{W*qmB+E&x^fd00h%iB z;hS^rnj>z;9%_H-?z_4|ytn!i!p$MiAqT3CX4BpIT(P~N0H{C9x(OwK<6#1;>3!hO zx_$cEX83PYB;>JSYrS)TP*c8H!jHxESqky;{`&9>UZ4nP>M>9%>^Z(aIg~)*E7)F{ z)5%?WOyclX51mVd6SE_kKYeuf8{epyA|-w+b%6SF<73Wn*U>`9OyM_fqW965s~s+M zNQG4OEbCx=kT@iMo`55Y<*7W5(VWOwv1)P8W-|3rWiBb`aT}jdXNrm|i61|AjkeqhTc! zYT*J>ThjH*2fiI#e!qhZdUN62W8GP=dq<{Q(vx&?5im!)4m$a}Q>UZ;O4C_105p#B zb%Nb5;4LRaN?thVy{Rv->-0M6Z=@B6M>${)YM}G%9MNS9a@b;&9T|+K&v9m{nh&4U ze$AJMbNaAmfo0#>cx5DD!~6LR)m#`L)%P_vB4E+p$};vEuL$`)BD5PX*|0wvKxRkV z$#9Ms&Yn94(x~-kAMVLRacIliSF+~9jRF(pYfkphL@=Z1w+<^&n^pJbQ+9C-K&?$G z0JUyj&U=WgaXeGUH}^N%>J}CF8%>J)Ce{_VK0p73T%S*ZPBvNSK`l-MBO{>)wD5_| z2M@_{Ou=0aA1IEp?mMx}dkwBZmxirOAzcodm`7Qyzww>JYn89SUJ+*MLrn#bsp!cB z8B;vOlaGT$!Cj1!M9OV;)9@Ptf-h~8L7$ELH4FfD2K}002JJdnbj{Zqwjr%<73uTU zYyQu2EV&P5<#OS?^=L=W)a90o{%@p9sY|PP-enVnT%cR{4vg>i%HJ+SXV>^7b5T5c zr*_JnAY%OUCA19r7PeK#RfU%}qIi=-8@Vka$1gvI?1xgGTfYP(tjWjOG*8h4pi(aA zGX5!-4n=JrR&B+>zyo`I_hRmC)B7ad@IZ_210fC_UHWx$<#$DZ^xCjZs{Iy67iCQ)T=j*NkjrR6hQ1YlHy>AzYmCQ z?=JV!!QL2uJa~c8YX5h zt07p)ynviFx}Ni3PiImyFqPMCaC2h#{*a7?U;Z=T%D?pe0OZC(^r=_(D}fTI1$4$0 zTQ}Phy=U~sI{;t53Zr{j?D>$cExWf7jGx4hBRaNnk>QN$kv%W1A0ab+f>C_y6RyMx z(hL6=hIKc-9+}B~nM>6a2Wx0Ss~A_{cMCuPH%S&9T2z$r^1^)c1JJmv+Vq#44PYR>LqPDpGB0*uMH^DpbTH&f z0eHb!^$YT-0e$t?hHx<_5aihIOa;b;Hxuk9z$`QqFKL6|0|9yf3_>fwlTB?au4nS` z(3$5*pPwOI^N(5(0DYeU#2RsUX_$=s%tRG`X?Lo$+#Zf`9jpex!8+VJdMDwTcfo3J zQ&a#fjGEO~RFW*bIE5$`H0H1KU=A8JH2fWxHS{>Rao50S_zoGuHhen>incm^_fb`sA3jK^X3Go5Aq2>% zdU)pxzp}ug1AL27OWsp7uxmn@wMck#O5^{q5Y6zK;;sITQ140MJ0I<;PN@bsFZMzG zXRxxh%TIv+l>3s&bGpa_|J&{qwn*JKrHRU=lXce28NxRvu34x>^8I_+8Ou zD_L}b3SUe131u7!T=!pK&3-$`gGPDjWy>*C@R|35<3|_2(h05UN-y9Q-@HND-2VEE z*6zi0DgZ@uPReM{_K7g#jhn5f63WpdGd_X<0^KPmz?e(JJ}O~B4fEhr4qkjpfWZLA z;Pbj>KKze1A79ms9ta+L{wTZx+tJ%`GMdSkt6?4)DhUJ7->lXqsIt~SVp+Nu05QC$ z`QEV-eR^z-(r09Gr7UwZmreY3x8hd6wdlP+v^Y>fg# zsF~VT=p1fzNV+VemaRV?%1R8#vAzC3jpkjT!X>RdQ}Mvmw!o+hSa7Dch%WAOfRXo1 zQ2IdA3G$}6*O({3$H84a>3}G#$Yk;YlJ|<7pp0QG^L=O7FbOMGeD$rxx%X zZZ&M`%0Om7$9?*y4+3&O0EIXO7#YpTE61UYVCQi`zwi2A$X#*XetdQSY9R5hQs3g1 zzbTeIx?E2X^AeOGUOc+=Vt0W71jYLw1Xt9D1K53%f3XVuK(sfW&C`R2XF7g9SD*`0 zI4%eKqhDK{AD!FC`jHx>=e;5)~fN{Fh&~k7Y9e&_< zH`&`8mUL`G)BZ>UdTq`N!)|i3@ng^R3)e1wzXXJK2bFFV7jpHPZO_)1I>8e+*3hdtAmR5rjN0ZX{#7Q*Kz;1ktn=(aUMeJTM84T3U^ zxhiz7!2bkT-3Ao7=RwTq)gBoG|ul~|&8^kYrzBG_uwz0^Y$JrLot|9SP6OXeBEPqtr1B%m1 z^abcGz31L)YSYDV@p}EM5c}5wEPkH$TP1+B-ROqC|C3g@7xJz!!|$KVdEhTk1+ohI zD+<%4M=xG{dYAy^FJ^z#@Mny$N+m)qbWA^wVuR?+x>5c+pp`vbxJiQC1Y z7xLA5+Z(dKHgVwYwuC0;IYpU&7$Un#K->=J@ZvbX(D zQDgw5PCWirVDbHQ_4^J0H|Xcf&kZ@dKfGPU5-x_m(W%9IQ<<<2c_ ze+hQtF=3_q*)0fH2IwgoR^aF9@By>D_c^poT2}yQwRP|>1g?q3^nYI403K0QH5Y&o zkXiPEXXgMkln<~I9|~Rh-VJ32aR0ZH`f2jnE9k7(HgF0*VyZGNj6x#Kt~e;%Dw;!@S~%{ z#Fctrjvuo~=mc{bC^qtNih@ocFqmUWZA{-#-b=qE0$>0wFh^=QZI%Wwkax}!UqJ%w zYAM_YMm0OhM*`Bd#gi@?Hrlj`fM$i zOaJ&A9}0$=3$G}9;#rB%NOUg(D0|`98kYoSx^KS^degO^$A+G--=Lp;HQ0{k-qR5LS3o7#1&z1t4J@r8T%7+5BuAVVVvyTJ# z@4s3GkaY&VizjEr>3>Ce0Hg^e`Eo)`m&kiou~GW=hLmGxyW#<$;>)*1m=C6@zar4|?=&*yQ@9@5Cvp}suNo`N!jvNw;T zY5zFJmvGC;@|1f7Z|AouI!jU_U}raU{}jF^r^6R900HA-h0##P z0ZR*+`dgr(Kr5Tb-)D5$`p$!<{be3Nr!Wb0AN)fv&y|5&dhTh!XR*TbTm$1X;JYO; z0od~^i7@q}Qnq_v0|vKhf$;311=ye=#$xGIIqFwv2=MMaPr^QVAXq(K@WknN`p@=2 zjWHWIqIKkX3?o|x#xRd8Ll=hwpZ%);McP|ORTX`0qe=?WA&2e;0ZHlZl-RLjB`;Kw%c)vg9IBPF7?+h;SsqHBYnvsDe(0e*@Mxd*$|n z4-^ve!xw-XeOyk4Fgy_j0Cfo02sdis@Rlv>hI}(wbhmFSv3(g(tu#GHiAuySfI}|- zsn}Wq)KHd~1IVm<&wXNNME*_Ov4%LYl2}`wAg3pa zLgW3oq0nvw-1sjhn}CQRht6%nG5~oF_U`7tT+A{AkQ~>vXr0MGa`fC(k52`VzMg=R z(Jn-?dfdpQYx$3avB#ss+Wk*)dHhc}_y^~<&Axt;+)pCb|6&zy0cQ>o*mYh5h1OCsrC%+ITT@Q#As`uVI6| zl|(&9a9D8t^T!#DLK43Na2OyFd>$^ts-9MAHw_`X9@-Lbp}!4{iu9^Oo!X0U*>c}) z!)$PceczDEP|O&>%Hu}Rtp1JQNF7J0GR$|L63LKO6y;4#UYqO-Ej}rzk6?U`U9v2l z47!I!N>b)67S%R6Q%oLAo?|LU!MZ*{ZqP~wwWXUk{sdm;(~nj9b0W93R5{rw#;%d+ z1vUg=f&%UV`Tp6))hT?q!xfK6-0 z6Hm?MC>n|lY>)=0h~!tpL7}UIc0BAXLT^I0;@DLKEF*?u<<%JoF0_)T@21tvJyxfZ8@w`X2U^o9Q-*^4zV4RxRz6qP z>*v?29sF&6_n41sPb<#A0Q1!pDRJQH)=`|~&9V5si z`B}hx_!v6YObX~VG$&en$Eu{W2JXdtH7QR`vhVCSMW#9#eIzQQ{LQ93iQ@Q1Lm>wm zIe(EA1FtNxTP&l5{uE8Zuxhwz(4EeH%L6b*+r!Q@CUVYpW1Oie6~``~-mk#KgRCw) znN;wr9*vB&KCT1@npMBw3AVG>CT68XPFn6J<{v{u&8xjYxS(b=&`|kk49}prWJ1HS zH#M#62$RHQ)I+Pjr43_)0|F+W%?p-YL!DRZ3BZ$|WNGRu0Z_N4H^sBn^-jK9QP;_% z9L7pT!7edQiJZ%(4jsMy;^;bN!7iRf-AU%ivM^c&oMKoZlW>ADnOgF&8UIM+;-lEl z^FB{)ow=6+a=o>h6^2Fwf^rMBnBpZY(14`Xonh-G zaX1$1d#w{?s#i~4JQSZ>pT zHOA35uD3pm3DjIw;80Zn$SsyUNz`1&;B=l`WdQ6*_<-9eWW)gz+!}#C`!rHG0~hE8 zH3FpAbF|vtsh%;8`e?->O~dJu0IxUD|H%_K6h>jgJt;%8sJZN8%gvrHGg3{cSZaP; zV*uNZlFEywkF@20Dpf6RoXz54e|_#nt% zwPgy#QbHHj38RkO%B?q9Y0}XPjl?c8d%nJ_o@o@Kq2QZY$t9-8DWMLHxN@) z*ad@28z+-9SXDW#Bip*uKy_o797gzge-aVR3<<@>61*gdRm>9r;8&0u^Bd2y>m|T_ zUs{xtpAiQ6;lzT)lH+Dc3|@9CTq`HX5s*SLac0|k|Bc~0iGccBP6Aj^QC`B+9H(vt zA8}D7gFe{rmJt(xq;ub7^kZTBV0b(YepsC&<1=28c|5xQIpBCY&He)m)|DzoD3;aT z-CS50r;;e<5fP|k9zv6N_OaXLJOTbYCV(_>Y5}v{-7RR1A&bhCNKJDA z^^ZX>0nl4@iD08vp_QCE4Gzjv2M|#m{QH76F$H%J>)k2@lNrHMst0Kml<UoHxw0eEayVW*Xe$J*$?OyR+xX%tC zn^#^iQjRQ0cvz=nIc<)Em`ib5kHRA z`fbs?cEAY|Z(9?4Rb}~~C`KhY1iMunuli~u3i^$IVtNkKk*z{ttxM)sZKzW&YLva+As* z!_O^|lm}o2HS0wI3O#){9e}M31Ls63ODF@JL*7o@6YR7k0fp_SQeOY6^`6;N5rwn) z2@wDdFEnqq!b$VzlJJ}Y*Np&M)uEXqRm!aWDET4=FeHj^*@b=ukHWh-K7gkIDl^Mn zu#PsBCjdM&!p;5-5W%tUNv5CXAWotLh=DY`UT@p_2 z6t2)C;qgT@ZM`KkDl>3Blrw5A{gQ$CQjNZo+L}(P`zD)OIR9npd|TG>N)1i+lVa${ zB3p`ie?=9gmLc3rIQqoE(E%TLFikyNj?D_8wkmOys8=|2!00}#s5d%=#+`!(pIt+{&Ac( z?i!6-yMC;9a}eeFrVxN`uY_OyD==vF9N-reBb!C2-$5BRe*wA#m)5LNp^R3p^sg>v z5PYA+{SuKu@st732^I$Y6kHWf;!JVW8YCF7>Oky8aoG!H*ETUX<@g(UHMpdM{%8d+ z$|regqJN%16mW`_3i_kqwXW&WFl0qh%_EZ9xQ*lv42BuO_Go}-10NRufb5e#*wy~v z-T80#r*Io~U;rQq20V&R?g^=s+F!!4-()lf0S-lBI2>*k@lPE?A=M3|{pHAi+U6=l zV-IMBgQmreC~ynAdf&&byH#v`3GEq zgoxlt6Ottms>DVD(vmFUFIgpeB-!ms_aI@9`^W4XtcpdpH@Pg;hkvp}VwIkkD{%$R z0;FQp0OBfmmG?~p<|aYpmN0lwGh;q4#4Iqm7{#6lKcePl1&~ja0aU$9%~agrXf}JztjOz zEn~e8Qe1?2tQpWm#)svs1=K-k1aJpLI=esc95Fq|qEQ3r@<|f>G`Xh`P&6`9F3AP3rbWh=cW!~7qZO_1z$aD+lTl zfm+;6tRkLeVi_QK0qUT-l%E0t`tyJ@R8kUzBmh+_olN)SUk6JRf<^u5Ax~(w zXUj;#iw$3HOw;IU&Zb624-a5?qHtB2MX=iri816X7Zv@U-L7w;Qv-vnrN0r7c>Z zU;P8Xmrr~v{fEEO@!-$^HAx1*cUBr0>y~&tp^CvTBF|XtCt;9Blv}sEdKfjs|1HLXPpI7Tt4RG*y-(6+k;@QQO>>CZQ|Rgtj8m~1}{IA{p@AoMKvH9mE-)<641uq zQv2m%%I|T?j}Pr)8xuNlw3+=XYDmdX`e<>i|FNRuC(C4t-_5EY_1L{hp09rzhRsZ^ zsp?Qcr~;U)&W5*g`=8h1TBzF2Qb4!yG8@TSmKtm(5-!siu_5`q^f29Mk^ftt4;iqsYJ@mpuP#meqe z4l)%MU1Nm(OBhjM5%kn2@jQQ61l6z}j4SX0_CntibmGnUyCM zDkiP68BdQdO~u(mI3censQMI%#~$9w4crFG3lhm;MG*R(*r)k zDrq{}-os}fALDV1OQ)Jz;&T4};p{+9Pd>B#tAaj5ZS<7l_V^s+AP z&Fjm8j31$cjXw)uTRJQZ|Dx%X6Xh45Sef4Hzd@OhhF~E8?3;fVy*Pcw%dh;(=$FxS zo6K*bFG=n(f$!J(xL0;$rx|M4+NK^?*@QJ=E9c}1{R(y2QT68jd@+x?bSG$a1pr(( zzoGW_%I#2vh7HEm{iA4n{0$6gD0;gS4qQAs0X+Fi-UMia+c_MgW5?~eOm=Cnjn-L# zx0VAQFr|#?(CM>Z1&21keAW-z{5+t_E{wXoQ6-nugddrc$8Q@-98G_$@U zfbNx;j|VIm)>N?AG#ihXT@P*zaYCJA0Q9a|-VL^Am15#Qda3>ra(j$XsIc)e9p_7L zui5_lO&%?*=qWLdOrP5yrzu}R)Elx|ZnQ>%7Nd|Om%~h6_K=CSl=4iGhcA9Of`R!h z%tkj%5-H-OLl@eNu8fj9!s&(%Ct_GhbaROCBNx`bipc8-?T1L$VHMHM!Sf%vrzAkn zA!D-GrGW9?vE%|mkMc{8%Z%zxNQ_y|dU&o{-@LR0^}8tTl!EpZgC@|@NzX0Z zs(69~t%(xCh*I!aFD}sJqEsP+j9s&sAl7|W7}zd-%St=gwL6rlpbiWez4_?haKGD# z1D*#R<}|~zUXcv4NR!{7_6)7`D{ZYy%|y$Y4QMXttj#AtU^uH(hACNdHliS7YdogU zj<(YwV=nwb0m*H=ObXU-Kq%?l`?k;TF9cy=XMNnYBDun3@jBB=O2!Su;t9c-^j`gU z&zs7<)hfa`e=`D$&7)E_c4y?D34Me&BuRaxOn@-2*Gd;bF_8`aRqx)G z=6Gz_V%75v>F%>?ww?u7@~x!?u4qF7SC-@K7j%QaZ@oRMy!He|y#xtNr8Thi5PqJZ z%=t!mArcVh#Sx#zcl{o&B_&p}GHl!^wov`%oWov;Qm;p12oqE^8U=Ae`bvAhQArk% zLZ(we$%;|dfC*|Hzr+N+A9W9dM`{iJo=$Z{6-k8zyzZb=ZvJNW=9YkKA_0!%(Ze?j z-{*xCQmt9EyvU`7dt#@5G}g3jw(2zZZ6X4R`wIC^OK`5%5Q@e~XM$17c7Mrk|hf^WEuEkm1ivw+0pE^UaaYiufq-^H_4s3IQQp`pnIge zn0f6)!GL+jY+DY=?Oi8ik6!R~v3jYgFUW|t)c~)~p|x{iJYWhhK3x8I$>h`sVD(vKXs*yHBKx@iGkJe`d{UhW-g-dp8sY<0sS$WJ=fw#z* z%z*`3FG}|w8i-adgNXWS5_T%pV>zj}Gu}m6qnV1BLrVtnTy8Ub#6bjciU@P|7MYhW zS}@X792mZvlRxvds`nEJj&;WK$I-FlD2mVA{hc4AL(9X zPvGI$!oC@6Zg|#`gBq8|4XPrXeTL7lAA*iySbg*H1!#!7S%mO2#%^C8YTPuL-gxt& zHDvRP561mft?@Sk>NO=W#5)!9^O#PK1$!AhK`EIZ!pp#(6YkFDsTcR>8w%(K9u!QB zrZenXufdG`Uq4MgQ`&jpz~s^cHs?#^u#Le=A{W7RXAI`_VRW02a8Hk zDrYPk_soC*M6q3kj$x2)0ohrnB$z*Iw$DID@PJ0tDH!pfQSbT)YSg zx9}ebm-Jc=mxR2hu&g|*sE{oR-}ri5mP|JpTa-Tu5d|K5~)>{O`s*faiSRdlFdr z@xVtGAwA{>T?DN$^duD0#~8wqg}Nw}e1`?AB(RLoQ3aJ_DiripL5n&CDnZ|gh5pFG z7tL$bOarUo5FGUUR3UUE^8x9lDdgxpxTBZe`>z z)qHECbEWtv00}T*xA)+)HaTjJk8M_x`S8@er?!!a(t-l*+VCV#LhWt}I6JEygi(sg z{?*)PAM9&G(aXMY-hG;NU{USjSsTxkdE0it9PI5uv+oHP3Qj{yN4q9^Rr`WF%aA=4 z`TA=Lb%-~CTSSBn8sm#atA%X4+wE91o4}*x(Uy|3jzYbw#L-?`9KjNr7uj!a$ke)M zhgtM~3SyrOF^9dyLiPnQgyAL*w<@qevp32PjLW@GPtWmDDth&SUV>Li=)O}rmt1Y$ z6~;Jc=9QmvhOgom02*Mn@coi2Fh96A=TqNa7KJ!{k6s)3OY+4J(JuC#P z0(tt9tQA*2ukUK8HiU2BqhmhURR$tt!Odkp&377tL52u~z+HVr*jj5unE!gJxk2=b z+heyIb$qs*o!fN9{ft$J`#3KMg;L>rD=LyMvje*t*c8ZVCAd26JA*n>d5-&t-)R?z z!>H$r!f>iCORdf@#>Dy;Y7@WuB0RX_sud^9Uw`@edo$k0mkjolcETx(QbswzEBjy7 zqB}d5mve9*3c1VmAe2)j1)tmB z%MNj_A){`b-2WB%-dT|J`e~So+@b)djlHXbR=x<5MTb_hwjZs{xo+6X!^94Kj_=G} zZbQfTGWUr~jLaD^W_h53GoST3iS^^1n%~PARcP>@RlLdMXCV`B=G3~4%57rwF!&jW z7-0|s5)^1 z0rR>GY-=z3K_{*=Z9+7*Wi5fHs~>TEkT7vfX9)n)(cn+2M=+3{Ho<1IMSUO`pUeUM zwMsCnE$R=T^N-QL3z`bDvc9^k6-D$@62VUd4!`NE>-5DNz#T3->ah2B2hp^L4ZjQTIN5MS;Oz za<@b5L{!}w%w186(N{Fsz0>wx*72vzLiP#>62gNJ`>K$q7pfX=8ax`x)XnC@>3BmE zUB)S`u-Kev@TzUb(#|0La{K!+35A&>5RoXN&N>$v(H_)DK+#{uKib3uZEW(El6g*6zz@_&ZRI64)*o#ots5k}9Dfcy z4$ZGCrt*x${s0+sP&uKK8(Q>=;|HJc3r&$wt!*;o#rE;I?h<~bs*qct9%#AyZJyx# zL++Pn7$0*|o<*Phj5-$SmCPKLg=0Xfe(Zqc&CUE{dZs-qZ1siEma0ojtInqbE0BQz9J9$n%>~>7q%0iK zkNSD9wZ;ZBsj}#$TE3ijH?7P#Zb2V^Z08c@4`VCW+UwJcPa=_cWuAt8F;Y6P zH5PamwCZ`mTQc^HIv`aX{pI1s8nYAR!*CBrEs?X*F)}ZY^R5ywUnI$peI1@o;S{S* zA-9&ysUo17Iym)i+c_XOaFuzP3udyOh8Xg*}I)*lZ;c<&`lGNgxivRGKW zPik&e^&q-S{w0DRwv4a|q(Hfj0=YY`i=Oxp^bq{#kIQ=2$%E=x-T*vBo5-f3&syQq{9p*Eoq0=E9g7+C^iIJlvASi zrZIAas}FxxRTe;{oM6`y%n06*vFC73qEaX!Q8CT}c$^iGUPW2Q)S7LaP~PJm=uH}< zq@(sV25s3fO&E|RINSrdO?6@aYm4#&7C$BHR;lF=*05|H*xrTFTxKE{Hyh$J*u9ek zR2JoUC($A|9iv(vDemd<%OGM}E6YthqGFOm+Wj&?U9 z))=eDMn`Y#$g%ZM7a!wt^$^ClEefnJhQMmLPk#*W1&z5PH3-op>~%T0yVBs}nEo+s zfZt^h3|JFTBG~|=7mY#>Pcu83N|gy+2ud^4`uKlZ$$Kru6}Spg_JN~A$L}aVfTONq z?=J(_fg}FQ8ZTnk6Jq;SiMd8Kik`CbZuNXxgZ-A34x@(O6IlJ)sG zWomxTu69%9OMm4{zw4-7gOl6$X*C71v@+^4s!{h}~ zeQ#NcrOY;`WE=p8V0I0w)RN`xGR;e_>K~~|md8I)c=z*^`oHGwAYKNt7*T(yTr?eRX@&{))JW)(dUar>(~mJ z*u4GVnQTj!m2LlPxuK$c>$qv`dBHYYje^KlsV97H0_&`5ZD`No^k}9Kw0`t3wK1$%-8qsm$5Om0~Bk#MpV|OyYjZUdivB6uArn^g=0;t^yR?%<$NS7AcdfRpUyg~71E?*sGt{y z)jJ7|(FxnBwsy9?Ik{Ai8Za>nbI zt!qWitka1*Z|PV}negkHMR>b8D8eLlG=(x#cnL1Vo8Huy^{8Y5kntLSe! zk8!1k7nlgu$NVr5Gwm>3oZ;q%91jFJt1K~O7=6d(BuRVV$70@P?0dW0Q?IMys0>;* z7I|Z6P_5Top67bAP4A59DdByUc#B*k4y|;G;6Bnrk-7AF14CPjCAqC4uWy)XYcb=U z;H$q2wWLwTv-IGUT*bx1kWaC?Cit3A0 zrbVtT?AGp`E@jnYER#e*?Z5-a#`59GQzk3pkCwhW?NIsbv7yS!?s1P*fY|8r`6VD_s*ZTW(SK)QgvFgoysx6te z627n4Q}3V950!rPZ>6X_j#i=Ld*a9Y$tdKedbu*_@Vj>x9rkb(mR-x?_(We zN4;;aK2vxky^E(m#WO=j_oGg$zQi??1=$ioP^v3vevyS#)gct)o27INhQ%gl%P#FW z8$%*zyq-IHyBb#x1SQUm8?}&V$lLO0e5N7R!)~m{Rr&Wh@y2`)D1Zj>Qh03 zr_ecgM7KHpPHfkHwI@!ac}P3nP)s=PI-jqJVZEE@QcdKi?J6t(b)BN4jRLgBcLlsQf!ua{AQkOT( znMsOW4!cV|;4e8Y49q?XFLw@X%|rwkvj zdz*p(5#8PU0|b4gfftf=-%F^8Ap{=7*j&bv+=Wu0_|~I8P>I|fLA!T<&S_=fwd~)a zP*xBg2H#yzdRQ*)OGDToFLi>)99c-C$4NM%Cx)}iF*fm;4u_}>CQhgZntN~2vN=&$ z-gpCA@jjY6gvZ{$*f5E^g4w;Mdl3p`)ipQTw$$edUkh*1!k&}-Mh}~vm^1_{3xUDc zCP@O)Y8~xw*?wV5l7=$;+KsokP9WK4CQ*HQ#n8Q>kW*`P!zDeasa z>-zdKis5Q3eem0mm(%fQGe@Xnw9#A+#*uUG=81xUwil7TY5uC!Zc5RXxyb0_<7PXb z-JMzX!MJk!et<+{+G9Qwp#+r+&S%>TNpG$u6xR%(yBSs1cJ#?P(oiz!XZQWgjR*o( zmHT+tZWYk+(z^``-C5HaRp5b&uAa=ZdIPa+=#M^1ZE##AwSG7=Bkn15l`>J#dK+po zW!km-0tI`3D7j>jpn~*vqY|Tf)Nv2xqN)p#drz9hy7hchUsJLH!pl@dYG9K5TS-N% z7%1l4EHit_+ymMLj2$ zh@))}?_`dfD^2FF&S{I;dSMxOIXO4pRE3V!8s?fOcdiGJ7r zXGc})3A+i~x~1@?vM)lS>i?%nGHu=gwcmdj?NETz;IRK6OU8S4lwABA`5Ir}jX`OB zGW(imwX533lvNCg03jXOf@#x@Aywq(%r(Or6rzsfb6Ragld0cba&V82{5@JgjO1tO zF-7NO8}6&>JsHu@Jo!*dXz#1)P>UFN;Q0w}E@>|Bw(nrrT#43rYEl%)93q(AK{Qib ztWIB?7U;1WJbuIKoa>96U}5q;L)8(()_ zzc*Nd%B)w?tZ+d(>T}*3bC_)HuqTk$oSjRJc8k*H2$W zYt)q5!ozcD{bsr%_C1%qWkS;%dS}r6Avy3k1lK@YS#a;Z_Ot7|OWJjIy_=~7+pXTz z+Rx^P^mN&*lN)RdKv7a}2&^EVV`hNxhF-COo?5zL=ct0sG1OIbl&jg#|hS1XX3MzR4IQA=Mj;=Q9uRp zTqJ}Fw!>NJbj&FU!UP(}VGL6!&ROucTh4y>Jv{MOk0n>P_678_L zttd>)EAph6vA0y(+*j#GrN&GFlgIE#98VQV>!Y2do!k7H6{h~r0sS3vsd0kVsxmh? z>pj)&nuPhWicD9!=wp^FEYgOC&Hxhvy>Dj+s0JQDt(_N~ z7nW4b4J`K}5C#f71UGS)<~T99rVAM#K#>_}+4r_*XMWD54lV~E+>8)jFKHmrn2yC{%((j0CMK38Z+~~_OY1rQYul&Nz{5=5MpylJ zm*10Z5}+(CK>dEzZ$@A~<#I9!!05arbbD^wL~QkDk8eJJvXx@fKoBciCW)2Y?^{dA z9X_R~zmz2|l@hl{*SaSh!tx+Mc_)^!7%=>LFT7msDQWAfPvjq19eh(*F_*RTWKZ1R zJsWio|u+3}6-w0tk1z>+JII*eEXiYuOLw~qa4VOO3Fc}Y$ zyw7e-b&kyhBwL4pZl(w7x6m>U`u6+>>=7WKKfQtCIET?$0K4A5~ zi2iNn@lcGtHcQw?9a%=$ns_op%EmOhYH;%uC?bGBfu%>`<7#i}7~uwca+%dM;7dlI z1U1fYT>JW<HoBv2WjXY1taPoidy7)43$j&(wR; zKP7h__Ll{hrFi@zBoM#6*3v{gURc7}73u1pmvOC6<-;dMcBbtaL$wGNl6zyXmG@J( zT*Z5~uMIw?%V%c_WwPzP-Yt0C&f~x=X3!ejnKmeiL-zbJ^w9KtGKpi*Ky8H1xR(5- zoRp9A%Gf8?nLIq#-j1Q_%WjkZsW+U0m&l;AeVV zEkg_gino;~187Ti&R{7vr01ethzrjX$u=szjgX$JC6#YOy=A6Q+aW-mm*m1tOXOME z;+JPD#`c%5l3s44+GEv!$Gj9=LG|_R;4A$a^m*cH;iGX5BJo!_y@m&!AhcOIl$PM+ z3!%sDSikFW7Qd^$Y`^9caza;SGF`B6evtmf9y%|*gH`c&@&|~nG((ueb`CYNQjZ^I z4R3n>A>`Ii?$@WSfjuoKhmE|I>!R1+UOu3AyFJRT@LXZc4nc=FLEiCaLXghg@q+E6Il;#9# zSSf#(yVCXm0L2$Rh8h-tOw%PCl#R~z zwvkFj9tRFNQz=@j_)&5%GIe68wTHj1p{4I%RNhX#BM)3de}0w3y|5xT%NOmtl~n^prFj^C*Wu+r z?)|MH=WzEA$M6;(s^U>wt3~7cF`_W9?_>_2nMZpHGJ24 z*pZDFMasUGdqhKoJ2Lr6#aClv|b==r>6k-Tx|pr&OP#R z7lrFf=m@3vDRYxBCy^D~fkz-IGHe?;n>Fr)16(NIE3_X{_$9d)l6Lnoc)r z4gP%SvAoa{(u?s^hl^y(1;{R!f-y_B=ow3@^7Eemwt$5oBwVeJ|1iZ9lbI2p_Lnqj zON$BRor?)M!f$ERa{T0r0KO{{elQY+Pd5D@OjQ&fhAy}3`8X1;Tw2vbaeASU-e^3( zL8Cq(j;|`kry`oP4*Osdhh}ID;|-VYa=7j=yuTjKjHNcn&x@YrVu86ED<^L9LYqJa z_TK$QPK;QC!}O15FVA|e@u5Qful=hUn#(+pZxy?)bmyQsaea!5v&pPk(F3v)SGgzC znyI856UCYBC(4EwZBDG6j<+_NRScv-s*3GeC2`tbtL!$gDa{1x?Fao@{F((c0T-yjwM4}XTJ|*LMcwycte=cqKO6GlW zLJ_?Y4hW5_uiQF;DR2u*`isEx6I&5@VYpue!NFwS9x9h5U_$M{;lY}76a(9LbrBOZ zK_S#04^)j*l{&U>{8WxT>IMwdl=+R%MhS3%g%e4it}RL>dV`cCYn8>;mYnld2t`Wv z4b9cqNsu!M%hCKhKv@sf*en+#u^36OmcC8v9le`mg^vK_%?gf9&{R2TE3A{f+?`D8 zFC)`jsIpwK@*Ryr&=-UT}ZLIWwP7$e;LETdz0 zAMVf+>x+lE$fmJ6+em6XD$bh9WUHG zd;0!PczMuWj?L^G16dL@VP&LLy^-X8+zEN?_z!f3xFBc#7;rnKN~hLv|$ z_sz0E=;*DRN~b3^D1!17k@;Mph}_Wq_}Lb+g=Rk_WX4LofLa%0MMA;a0rO4zImM9O z1f^|d&_O4rbyiyvt6|S9i5GYNOOQEY7n#7`42O zNs%31AT2u1hlEe#MEYMV?*H504zLzCb1E3zp9;hT%nQZ5sC&@Yr@Ur z2heEK($Kpr%v+{?lj(HiNZM#Ym%Nqp{fJX-W1Ic`qHCL!xF0Ie&3A8@nlu+3tD<`n z<1wS?sZHs!w%nS*TTb7XpaeH|;%IK7gKpxl`EPXybChuDt$3@82Z*DH_1D@oM9 zdan@JH&YPOBkh?Q+&&y5vu?XhNO0w^+Mt%Whr9hcN@+Om#f2d`Ro5DN$J(yD$S5Gl z%rcE`*GoYxK9Y#`yf!lbb8|1>#X(Lne_l<>yVOzo)E5qFdE)+*ZO74yC#M4P=C8Q=xzS{q4TW+Ghz`oC%-_QVC5^oy{q<$#=&QQp#p65)uUMUwxk`xy$- z?M6whTY=lBX0?^ELTkZ?WX1bsnSeoK?A?9=i#MDv*tSs&8@MeWjlEp=&n4T-^Zc=k+QKdJR;AjrHUnJ9 z7jiMy7BMb2L=Y-sBO~~mmYq95s(jXx6r(?s zLwMMUNgRBf3|E)8I*ipzXkD$+T1C@#j3j*dY67j}(=C5!P`ihtp2$Hov&z9y++Ilw zVH4n~q!;BDn^(*O?h&hHoME{2lWTm+yq2-}u4w3N2DbOM@R>E_*_+|I6Dpf~Ffn^> zV{M|q`l;*dj=N>CO~E&)V}xUA2&X@W_0Fr`kp}B7ank_8S7rt2QuNGM4@)&&GmUBt z_uQaDZsi8WJr9r;R4T-Yo5PLG4RjHyM;`qSZ@@%@NmRXPt5M{=pJ7z`_d#edfqd_= z7REO13v;4R7!dF1=JRZllqJ_~Bji2e#TCY2gs_H9d47I#(G$Us;TWe!Jg}vT;b$BU z!8|R6QY*g5tNTV3eZzngo~iomeIs4;6;dx%^a@FM=9|flH^`=jN+{1ls?_}s#9lw1 zT8G?yiRK>MGNrxThNb3)mI(PNOIiZ|3uK5prkzQd+0}F`aevKi?ycU-ZSGRPj_ZTh zzaxvE$vKm)*&BEFE^ZKn6%BBgwda9z#RpB4VjMAso;X>yukZdJ?%p~ot|wg^O+s)F z?(V_egS#iVyAvQ-u;A_v0fL1jxVr^+0t9y_XyeY^4aslLIWu$aH+N=zf84cd(YvbN zlJ}|Sm8N^|{y)9^_np{Zp77sxVt;wUuNWehoza@ixvB{N)}=Uet`dU92NHYJd7U*s z?i(Fz7M2R#kCi{O8B9_Qp1rSPnHm?JVZcq*)OtQ}v}?#Mn>$$>rtID%5VP=cdA=pW z4-U=3X?Aqi11Sx?s3DBi;^y5INxSDuhVRSiR@7>&+f#TR702o-hvC^B^!BU2oS`66d;Z$u1EUaXF9)}87vAla*-EvzxVa^C@$NpK+@FqwB zqj-X9*L%w3)w0eC?6svyDNRIqfzM|)IA*i{U3JtwFZH6&ird5-#RD7*mA=9$CEFrm z`}A<=#a@K(@xzVk$*3GTtFiFiMYqOB4s+o7w8_&3)Ye|9Y`u-~AiYD0@p@ZN+HDX( zpyz%?O;Jm8x)uSAAAEX@jg(lUi?62stD>3lAXx5ylERK#?59dg zUrj0|dWU@Iv70NC+8pWX=WwKkFc)(~_G1v|LWghJE-mtFKex#ju zV(AGf^^d`C5h1p&KU;q$7zc;GA20}i&m=~@=ZNbfGENpQ5ZS(LdAi9V+a9u`O5zC< z*#0wcAmSlGvOx(D>E|$90rv|~9?cM8j9zAtMa*Yqo1Jco&$-rWlISIX& zER_H1?*$$w%YB$%vNO7JnC5W{4+H+$tpDqvf4T|_3$X2+f4tH^9R&AJw)T5~_-9Xf zgVzWa=pwkC(T~39|HI{fDv<)RF$)Y4&CY05=G-^`N0&1`By=s%MFiO8gy9e(EUT^2 z16)kxtVjsGf2$fZ5kdtTclnT{*y~H0-gtv=rNysHoYR}0B5E~piJQ+gav>89LGO%V z!;N6Oa-FShHn?tc?wH5AUgZ6u1Krvy%N7c!$r#!r{p<^^@!8Y2A1wKEA+(UUTPH+# zN>8Jatp8_q4Q3-D&mp&@_@xap9p>2l_F!^HHqLQ_$RTA#zdeV&U`c;Y_`B9MBu@`UZfYxrsDSzcNorTiQ^yq$)blT*+VGesq zu9y;93B0B}%u2?I`hlaNb5ZRorjWyz{Nm)!^)LN*nCH%J(iPvkiLM!GR!(5<5-ZVZ z;1B(@)`nq~h2EQrHntnD-DHI-oI;gz_LY0YbMz8BB`40=b_7ktEF-kOM=#cphN za@Uo(D^Q(n__=YE>ZQ=^)VO*ewO_C((J5(ZR2lj$ZoF$r&SuyN(agQAzq(uJc{NrE z-E_uNk#=Ku8UE=r1ocx_L(^G0DpKBAcP}0x{DF_&(&y-^a|FJe%xzrX++G|O(e5>? z)*CA|%~=#7>H_bG=b9P7H@W)Aduq_?pY?=r{y)Q-T+A^SFd?Q>s@;v+6EaNCr~rq^pUbU!-@a5HXVi|E`YhLv?&nluKVP&) z!Wu^=vQ*mSFB^5x@6o&T#0OopYoCe_Bl|V1Kow8%m-*0H;_Qa{iT86QOULFqxT{zO zC4OGjpQWu5(Mz2&ppUSQ+-uw!>Fjvzn@QYVWU-@@yE8 z*+`Cx7i2JpeMlGciyLEfs*HN&5r$wBNL@#5$4y@xNzh7C?(Omutf4b0#8uc@7ldnc zqAJt8h;MRhF3@E;=ns|G>)M$} z-!XCD-iYN?^TGjr$@DC0JYF4_PIJ@Yz2Dh#IvKn0R2)LQExs+~TXovkXWVp>e`flv zST(I^KhTRrAKV13O0v4r(s9))GCt;KN1!g|W@uUf+<0#Etyp8e1mDWFVz;>&)Nr%K za&VN>-`HQ$;9;9Ylt5~ll-2{ZmbfJm>1^nDy-RVq1g$RXczL7)7f!myOFNDIQ6Hy1pNREJIr>)n%pxZWlpuHy_y{g;NvVc-UnBqB7nz|aMEucSq}={vgE=D_U? z=S%*(Tko5i)nr@e*xNEBoeKg*~5}C&!QS}&NO%d z(Mto8^+VJCJaU(y!B07%qrI^NVMcTvzGV+CMGINO?H_3TmT2 zdV5ZmKrX>OtHrAws?#w$V1<(VOVGMNR^Lcw_Bc>r)(CY91)vQTt32q0%0~{cF1b~L z{Eu5Ho+X2Q>vJO_*9LBsr6Ipe{kSH(we`^&@uv>mWMHgUZ1QNRAXE`ITTQ=GL32QP zghMYsOS`-AeK8eppb-)YwHV%fnHOy~t&th%uEmknkoGCvu?KClQN(m;-_UVOz?$UJ z;Jj*Kh(v=G8Q$Vov_10l<;6%rx8~*miI$rfyhFXG+E5QJH2n&r`xNG$-S+uy`={lB zYe_^E@9?B|0<83e?pFa!tVjEl5rS9JokTj`^MGHYVLT-~UL(t&1h}&n#piaamS>{3 zsqA|s+izp?Rmra)bXf|%rD;V{diI)0?R^HhM<#*bJiC1p6kuH zqhI}4U?QJL)p`l!9D&u#vPJHG9t@|2au#R}!`sOa=SFc)y`7@Os-`9GgP;Jms>0eg zb2?o^@tP;v-^-|RncJlG?1u_pnGELpaZ@6%nkDFC>%V~S53M!46xroWI&Wlzr zxxbkUB#`BFJyj5oYLnRsl63Fcdw<&JOoBBa=F?lilp_P#j8t|SMBiwy|I-#}Bk{ch zlUZxGc^&+QbZF5P(a}z(;1?&U$yvntGXlyod9=&T8^+j>q~nWS1kVj^ow>QnXvCIA z4^q9NnybZq->5J*bxD4OtU9PSI5lo;v70oVwV2s_aSX_ysC+$VGV@_r6;0)dQChaK*%Dirng!{RI z@%gkTugBUO$^$QAHzf*ZAnt_XzPhI6*l4<7Ru8vdJkY-6Uq|glt4pCf^#vo(qH8`PRLfNc@Re z8+h;6Eash&J2j#CmCL?csH$=PQzB=T#JS~QmQGcxbyVY)@7HEmn_fb+?m{*OChx5j7mzluIP1;Fykx2VIV*Qv%Q=!6}d zmMvHj9`yx{W9klNPts@U*yl*q-YCj4O==317qvA>IcqRT+4LtES<`8LTvdnN@Z~6@ z*Gip9-1(M4r8NYH0@CH9VJs6uJBoau7SLt$>L?(Dwrz?1qu z^?!5}O5)zkZt!5(^p#$&4QFs1g4Egg2wy+IjrA_Yk>LXLb_h~$^CNuo5I2@bI?%wg zseR}~=z8SDLG!n^DEjUCHK2W{ZfWPEW7{P6rfec01_ba404d?xP?5j>|8sUx4;^!U92npoN;VotYn@cJ-pv$+PM~8w~ zo7rH^EN7`@m-)h;Dr|hFJ@DXCvEcr=?5G4JY@^7oww8(rM8QYxM{I-Hqo!@)o;j4s zUmNiUw&q@YYI!oPwq8@SK(Zov?6h_xe11A#G?>sLK9*U>H^XB&p4neg*6UsxrpK4CXJmSP+f(?p8MV#ll!w@5KkvuI>>3x1-j?QX z9md>|mApcRK^_)+m$+t}`BaWH_l?wSCUhR?W8+>&S3#%2XkGdfa=b=58d%wq0)ypF7}=+fANN*L-t72`oZSi*mGb4q9lJ#*n@K5BY66m9~ZsLjA?Ll#{L;wa1<2Z#P}L z=6pF2_f?zx)Lc%#nm8IIw7l0@RE66Yp6s1V{8mwib7=EA1Cx+7d9Y}0&~gD1s$saQ zn&N2@HE-d*Miz-vaVz3J*pQ`dhjgDE+9ts1uD=6)x-<|5OsbC_jH zMp(k4x-br})`Z|{a9EDzpyq?sWhJ9)-ui8AH$aA;dxTXVBQLFral39<^7}=?TIH+? z+*|pfro=c;%$aux9<(;j<-o2WLWkCHOC@TGIiTg z44;`eyDWm??1d1$EO&aPL`Mbx2dSPUeY{H^uaM8hJtn~W~vosKH*c6}k+ zs1wkzQ$8v=X_6OH+|P%rlawG{&+sU4?4aWE#t-c2?<=f7(N3%Hd@Vi1ff+r z4a<+{91NF_zNA%xPMzT!v&Rr)jWjgEB9U$Uy{}$!szMuXmUw?VuzBH7*w=WsE3@kO z>8{cf4HO^Vs!i0jRuSIn%L&Wv3&rg0Cdvc*(^X*YaDD4#{JO>W*Pi%e(d|u;aZ4KT zxzQrk_v7aY!jW4|7PyvQd61hBE;wk1#}q?S+LL1yXftX`((2SQ&F%5FOFY$@1r+n^ zwamvrZ*uF-OtZN6ccdFiY~UM=vIInhSW6tao1yQ-JPS`L3K|k?yA!H6+qp98N5vy2 zGbTcW1j`OQ{fv)u^4f4*526?&6_D;dUyTUxUIh$&_59AxZ#6sw0?Irp}^XT7JAJGbc1F=H&nJUN{aC(W8s?8aSRhoR-2 ztE)KpE-hJxTJ#j1kq>sOw&n1Yrp|4#rr*9EtF@HE9&wDCR3INw_5^U8wn=8DUo6qi zc-m;4whcmBT$A8(IDW4kY&MwsMW&RwnsUM-!P`@Ibqcj|O;V2B=w2L#ab%iV94>&g zjQq?48)WP{mAX&0I;?m9T8po&#kCXd8h)nZPMgu`Y}0MhgWSqf9ql@7rTo@DyRXKT zGOkIWN|iR!H8k_#lw;XMG8GP_|t=+X^ ztng8&IRy7BF9XaNr8D|sWwmrA>fjfqQA=Y7>T{!u8Juh8y$(MHzg@&tZ+h$AmM_FV zE=*-RB~8Y=cFZI$dN(!DkfClJfJK{;tiQ2v8M9TNi~0SlSvwROhzyIuXc=c&#cJP; z%DVAbm5W+IxNV}eal9w}v_D!>5LOQl)JVXwK#so~lE>MD^D`&*0wtL!<4Z421n05v zqR2pPEouecuT8O;w4J??Y`LCZ6f2!xRQfAPb2tQh>d*)TmzMJDrPs-+q){<`oXnkE zgVzI%pW@$n+E?uivB$CcYN8d;?=!f5gwfLel`&(;OC-2Quu5I_Chay(Q1Y490M&lK zz;jA|2ge8-9i?iW5-s55E1a?;IACqZ_xNO7KS?naSkyl83s z5qSEM5Tv)yzvKIHv34)X4VIzm%xD%cMZ|M21+mw|rL#lX*+vxK_b0*Wk|4ktR*T=g z>nxbuYcjD4XPA#lvifQqA){j)0TsYb8DD5$%D+5W_#@9r5A*~Dnf54l*m#S!w=3-R zi#jGR^=zr1aLh!n+y|`Qs8Ch9qj6agAkTp~@Z_caP&u-7s0@7Bzu&<0N$61Psmc6! zVn1T2OShc8_D+IQ(pTKQq~ggB_Fgyrx%*610Ke-ce2qTbAfBJYdRWDrK=9)DPm89GYg?LrH z@H&;7Jl?w07@|d%6lUl+SBZH*(tu_lxQciKwZ0|Pl&G|>a$IM7@s&{|JE@t;iXCx5_3rjyEHVB+~-3GsL%2PVSJJ}b0t74#VB07 zqA(u`NuLiPqdubwhVfw)^(Uqw{T_`U)5VoZ?mJu{K&RmZPse-Dexwq@@-luer7aBs zp3Lcdw=`PvBAQ&h?KE>Otdep``Pu^bj6zBUHZ%d$M1k~^78TlwI5-(p`ZF(eO>uFK#1X;~2o3_r1O(hO zyb|VIW)YEm^NoyyMAJFKw9>`I+WM7Hy`i?W3b-R~$kV)AS;<7z%J4{_7=y$;659*Z znlqJ4bqv)EnR)~&-rg>R8|J6o&Q zQc>=iygUn(O5Wece8RwJcj=*k0LqT|IdO(nbAlH#U7UCHwMfijO%?YJAs82$eMLVy z3D!Bf8{Kl~`V8Ih&6{plKd57Gz>| z-wk}T{r!Szdl>m)v1=adfkPvnKs?;Sx+?=i7+%WzB(i;ONF=ngwlp`j!$a?+z1Aq) zg`dG3oizZX$v#dpFG)FQtqvCTpFXqF5gM52gjc2@3_DLO>i;D7lkH8_xESjhROfRC z;ocWAgr)LtgpP;j93%OACx|VupZkSdZ&b7q#1#sej$%rjXY%j0u$SSI3%nYdwyp9} z@q5?1h`noWy%}QqtOXSsDg}0eIOV$pd{sVE6^`-W}YApGO*D4m)Avu|qLytf@A$2Yg}H z)v1>B`%VhD5>iCCFgEkJFLLk;%h}MNr{fx;Cn6{{{|e8y31R3pQY6jnpGe};o-{W}r)ER- zcY9@em=si2$ZQ@h|9qpID+wYo8TQ6Y*$0hAzU~Jbd(`&)u^UBb2qK1oiiSx0 zFIgd6G;%H@-_IUQA}Rv3MzKdq)mcpg#31$9-1mnF+XV6&T;J|)zWYXNON!?195-ZJ zUe~)yx`>ITRDAf7m(UNOGJd>p+VP^JC&)KtNjzAmK;9>M%>_mTBI`$y zlEmHAf-#&?ZR;YxLL!j>V&52vq33~Fd;YNBdpC;h;WK?RgP6S)E~LPWqe)dmBFw$@ zina(&_%O@Cd?_}L>Vx)Ja>8HIzkFlqz4s5y=oF$p6rj zbzYUlO&nV9@M>-3vl1Rkg0?0O={`rxQJE}0fidN%^No|GtMQO-kXsMUjH|e8Ibn)a zP?%gz^;;U97YeMY>NXB8ZM=qrmh&oUl{hD}h_{ui80y(LoX(;x)vumg#TAvu$sJ{p zdOC_*ylw8BjR?ouaZjB#Zv0lkYFjGR9XA$of5;cdW!OK9m-a4wlA?23w|P3%{H65A za6&7eY069dV!OSnhzVSAim!6wV_&4<&h>!@q0Qz_J}U_{>VHkodQF}WgPs}Q9PK&! zNp5;APNtxT3UNl%(sOhlV~Vg3=~{|so)SsufcI`cwnhI#j>x2Fq-=R{vXmra-M)i= z$;Aj1UxT@!Q)dBu#-tA&LnozL{B(3pQ6)a#kqb1abslu6_@eU~h7b=_+t@16&)imT z$k1*At-?+n5Ps8M+Uqf*?4A|}8F)2;7InO@9|n@AinDCy(?t_M+3I7Bv#dVX)Ot6M z-YGx3579w8x#KUA&6AWZ4F7c=F*Wwrjv`Dhmrfxps#7dA1XE|R zI}dUDPFJUC48)mux#HS!v10QPO68qU3ij#Gc>{dR4%ucI*mc93nAjY$nZnvZ(2~7-HmE}!-_?*(UBNiC@a4Nf9Pv;;t+A(0 zjlL-qni*xH-HtKYB}p6U`XPz=R>h|&Yfr0eGY>`0LO?KPiP(JQqPSV@FYwA5IgCR5 zV$Bzz61j*+$3$|mJL;&Ac*0XQ;5X*bR_6__iWtQ4FU)_a+0DqvZ>?3V$&sH2Z!aBW<@ug%H?$%z8^>#t7lfpQ)=vxzo<4NhRnSiL)e_@z?C<2gwlX7 zbfj^%Ot)`$-M2}%uqt$XHI4zVux5Thld5jYDG^q2s_U1HX=&B4=7Vb3FLdqHL%?Xv zvF6Oj$!gKRs<39z(-aC#R&|O?=A<*-m6%AKDgAxG7zK1+H;9{%6cMj58WMwAR4wlW?H<#hHQ z?a*`{lla8+*ovQrtpp%v1#v%nm(|Nk0aD_wT%6p+S_J-a9h}&S%~^N3?6z1rzI)?v zk9+m?ApU_cl-QXaHw&337*iJeXv1(>}lsp;2?pVN?Pn840@VS*c zMaXbftI1%Xp<;MFG{ui$W?@+cwv309ihm_I)QRRNl_9A~oq>J8;GQj%R@{r0R8O4& z4hRucGMbgHD^Ob%eo{#sEvwwne0h?G0(i14y_)1PrG@-uJ?4h$YlIR+1!4@H^7Nw{ z9d;37S8Hzbh~<2#!KkOT3Zzr^u3ps1X={G)xhSWSllm1}k&K_V4u9p3>g73lTTJefaX#6N1!9NH(8+ zRZei0Bks@vhCO+)$aSik-G&?RClvUC0hII`S1T+G>7T}k!1A>>@q=xM7 za8J=zrIje}?E2w7243TkcexKejS)&Kq#@S)mMsKuLUf+kK8%o?rKk7TE=YlU@-kJ> z&Z$PjwL)RJaIHzBw*gsKD>ZeWBs8;+%-;2QyrAMP5qPtJL ztIX2mq9?z!oOT>hp1xbu1U__vFC!zp?YpynBY!*W?8!-7*s*ZOG0T7Z>HGb!G?}~2 z5z5tFZzZ)e!5|Jf&HD;1!LtUvOG^2aH&+VmqVG?bI;t;oZe^37*b^WJ)_&vT>owPV z9*o~0z}7c_G~aga`jZW}wZcVoGbl2j2xg}rZ^m#z%cwbYjQ!W^;Ihs(#MrN%Yfv~^ z))WbS@3IScX;p(0o0Tc-^V)s`V7N8=HO{Ux8m9+#j*Kvjj4R(j%;^mO;YoKp=@<>pS;OtiQb`xH@7JCb2P*DS zW)X)`34GY5d=VAvx6?C8p3%rhfd)wLYr{X&*|Ve;H!du$!G34VSsAmWBl-ZDW9`qH z1?@2k;?TNFeeS^$+)GshO^Dy3dFfm>p&3S=xd!odDGo@I6)4$;@9kucST3ilb5GL~ zMTzdt1@AV!mx7=KE*JZ~CqE6(4Nz3-Eh-L#HH3ETq@)zBv<;|&^i6*>$E7S&XGY*a zLer$-G!QX^R zYqMu_;ox%SGmC2GBfytv?;#9 zg4IzcQy*!i!xB7A!A(rcrh0ih}+%Umc`(UDx_Vv!^@I-}T=GWxIOjJ!Qs zv36sGK2~>g$WU%E8}OXpp8BRu>vf8pl!pdwQOv02Bo)B_f`UQvV++&uom})!pFI~{ zXgMtYIhVBcJxO(m$-^KNR!_TPpgVo%cuUKveLE8y)F~rLCcwYkdwhbT-|t8!pjHBJ zfj=B^p-t8GN?s)Osgg)4@sM!p((=7+%@ez`_F%iZ1PjSk_%&pI1464;$T;lpWa{R7pzU zx{+SsdN}lkk8jv~aV7oYdzV_mJvhg?=Lqr&HvxpHdt&YoL!}fo;BVxBAnhpUpghOY8Hp z>dzx%jg?rFMD4YtssW0Il<#uypOy}w*=1?-_U;TAj@tbhuixBFGb-g&xKtY!)paw7zvIQmkv2D6SK zER3^(ZED2EXNVVC^mK@LsA=rf1kV}@^`4V=0W=^F8Uk#J7f~}|Xu+cPP~Pu5FIsAo zW01fd{=rLVb$Lhi$Np-DZ={>&Yc}ut5WxG=VPDv=0SG- ztj*gyKUq_3&c&>Z4IM?%7YjQ=>{NA+1__8z&x!<6Q|uz)a@Q+}iX4>(SJJ9FzS_;$ zu6k6w=;b_Riob%2v*W1@{pc}fzgV4-$fC)T zdRF_*1Ik?=S&}qpSjXN;?YknfAwrJ56J=$o;oD3pYRodL?q=>Y-?rpc8fV=@)IIVM zXBzxsW11RS!}-~wpY=095;mKrE;gOb&iP(WM;pfO0=2R57gU$tRjnGOI8pB{Q1hj0$uUeFRZqZ1xZf6G6G&FO-+>+Pu8tA>|AcG zDTW}v2)R(@cPLAEE};v>m?Xl`?(<40LKXRL8ssmN`m#`ye(_g+{$@0GlK0pCp^V<^ z*#yn;*Crx+-_wQ{)BDm6^}iLkx_13&W_q(L6o*)q;NLxMQyg8H+9xAkZvfHe9->*X zic1zTn~Y;Y_m&IOBy?A-qKGAoC}*PIqKm*@KX;$IHpZMUuvazgYGO<|Eki`#ZAen* zARy+~$@~XNoi!`>*-L@T@n{jMwYr=T2-;s8`MN8=M z4g-0ZYevlzok)LGY)v}#(i}Z7=?4WFHj$GkXWpc1XD}Bpp^rIbtubEK7jFlW`lK4V z+!odam9V--?L=FJG?dacR6E;^l*mnA1-*=y7?Z%`5FKO$FZE{VU&Lh4@v&w=-r1F9 zdll6jm75*8NTYa&`}CVp)5wa9Ps7|E(UrKiy@VFMq7fa(>99_mLyOTfY6xcUr!MF0 z_v0QdIrHC500|ryrVBA!nJK#F2`wkU<|S9;x(FJfx7dbU#IrV&SjFEqH1yCP=ydLW z(CGGKFQ!GFz~0j;1X0%V6V z9NyBk8x3}TL*n8gXQV+)9mk#M)PbKwX;$RZ{BSI8N9k@yd5eDDEw-$k+O+w^;J1BG zmC{*)S@RJH?+!*~E?VDn<&pJ4C+jCyw<6ln1sPnWUaq$>0q@0RC-s<4^0>!XWRW+< znO6}_yMbuKDUlSY^-~6CuIiq^imdzP^gLq|*RJz3ZAV=?LN(*WVFX7EyV?tX55cXy=C4$nx3f_C#;Kq9%$A1!rT`;(KDI)5IkCS;RxKdMkXA8C zANW^jDD27{{k&E6vI)wu`p1?16fesxzKy#U&Vps4>b;wu@-x#liZ$mQpILll5SN6+Qql0)k9ql3X$3ZUAW*76 zMTN_m(I>e}$!{lmLqUF1c~3f=MqG?QdMD^z+q}0uL6g(78-GJTv=R@3t5CtM>`O}r z_8Sisph6k{v7FqY<@Mb_Y@xtFX741sH>KX>O^Wla$DJ&L#IYIh*7t9%9y-TnTih0R z2&in{fiS6pBF_giNqjekQG|}ofbA7J_H80618n@7^Qt} z__`J#|^+xoJhD3sGY|_bOZ%BTIZCCv!+NE#q(_NG+5| zt4K7L@T8cEP>rGoIU{*Vm7-pPd1};j)<)Hd{vc5;S->HOP2d+Ct=!51i>GCYT`Z=` zflYa;l^X+7`7MTw7x_Hn9-)~GT2)7{zt;MCYcMj#&4i_{fV7O~io3#=UjrRW6p={w z#{|V$j)-7uEPGTXitp+5mz{L{bXm;}=&0P4eJP}*-JY{o>N9~cb=;)&wA?+6O&s@`6JX0{$%K`oSPlr2x`R=II z43Oxvxi=`BCB)itLh8xBwT_VXt50=*hN2YF1oz0!hM3E?E#hA+_z-D}F(J?&yFing z!)-X(+z=t%?6wN(?7=vmEVS^TkiMaO{#DKwCIPmM2F9Ky_yju0+0%G&2-Yq=SCGH) zG?vh2K376ZaQnxE($UwoALVG$a5N#+1-?Nk!inMYh0#yqgwZ>i=z_J>P+W?MZTVVr zQ6RHWY^cz)iT@M~A(B3SVRV1tXR>fgDB$vACJQX}Sc+ialqer$^_7B!vZ7yA2xG>h z8;(rKNs*737jy=j66{JQ|Nq! z<6#4+Vg8au&#n>yOFb4%RP=1w2idl46hC1SAJc%B)HEHRiqfKm5o(;!l_ghVg{4=t z-$=fsSVdlgcvZ0&O##73NB*cAr2P(fo|P_$2X%fp|CtwP{cGw!vRNoa1izIA>mB+D z#pF>OGXNVM4wjA1SV)Ql%4Xgl-LSSOCtCPL61sBiH(gN@8D~{h$w%GbRwgh2{5m3_ zA64Ib029CyD3zqtqA7~OzKVZjfj#j40UkJkzpzJt3CB#0{=!_f$2V3OVWu9IrsJ)a z1W5on15NIYbz`t_;$r6GX@Zw3qJs&b4#q=fq5-2i`v9i7m|`#spwFM8e-%qNm$I z?3X{rE^pS;MQa4tgH~whW*hxQSgRC7kI7^ki?v>#7Vg6)2*KJJL7t5Rq?_^9X0(Ye z&ch0$yKT$+mDUoZn|_}~ZLvjy;~)rc9+dQvMr3QuZ|3FOuPWqtE1lx3$0BEWq^m8u zZFC@sf{KacY&`QMw9L4($n(lkrtofMrTf1dBr1Q9l7(qF!aqT5<|7my}t+ zT#^o)t%sAcSdNW6R}_nmWdV9g@}9(j@c={O{5b64FP(3E{G+&fuCbyd&B)6HdKFr1rYuV z+h?->1rqSEhf)NitW5bJt{)RCM3VQaA|5k7*BI;?0o8fnzz3j4LPM_|t1K)fZK82R z@j0TzD1zPMWos@oWES(SBOf2|0twoBH;;v@f`Tl6j}N@yjyVe(_4#*IlA6n=j3oPx zsDF?$f)fT8O(ieQ8l zDIa9@QGJC-{9jd|V8;9FgB6AWD-7EPD4a!<79bc|;OFgBMJ;^Vq?1YXQ# zXJI2~&~V2>Kb1X6bNB>47RdXz0LHTBvQ|M4=ssZl*I^|4iWxx#bUhoy)lwWy7SiG* zi2y8&I=8HYA|SqVB1wI}Wg@_O&hs%iTy54mft{`WYJDsM*avTA{`XM#H#hzN=B#H< zQ7h(JHrMa;=HI)%3_gBBJ9RD((L9|j*y6I}2eHUes@GU(a_08rb2gRQh8>c}K`V0c zSrp=~yIE0_PbU5`GPIZTQfBcPBht;U))xGW!8a%K%-&pw`4B&N2d`fdz7mV`hEBbm z%_>SWzIO%;xZK;!_Uyn@Bhe!y;!U@&C`sA7G;r{Gp${MZ_IH$ZTEF<=b1(;JrTLg8 z08tHy=6yHwK8lh|*Gm3Rv%oR+Uypi)9L>SPhEml;5WBx4xnTG2xHk!D@Zjq2@&8fm zeoFZ#*6lrnzeikEp9fH*J&2c5cYIQm{G>qwFAc|RzC)Rd(r!Z#fF4_$8(kiJK?)bb z{xkP?R5=;`lT0|JV?AU>fc96t2LE7RF%qZ%;b((z z8j8xnV9EqwgDDdYrc6~EmXhSl1j;|zkar}Is{w0q7WdaY?EjAYzb6cT>GwMl{_9$D z^uV=c3cAns2w?KW{Z9LS{0-eswSR=}-vaG#?fGkI&oIFqs8cy`E=-s%E4=t zCY5{qCKaI*dB`8UPGaZ}m$!}&)vENZ^ghvp@z4=o2-!a}7@`k@tRx;@9>-OYp2(YI zyU+$NeAVAhhY~wLV7or|jrGO@y#0|K_!w_^?R#7LX=lQy$uTkh=%$2HQw&abZMCs@ z9@5(Xng9O#kcOTC#Gz>WL!7@-v`G``2QVLVw7+tfzomn}bKZYBDJ(o_0?Z?eiRO1I zd+Rt?4`zC;z0T(cf}RZj$>0J117wA;kICXAO@Y7vB2x$9xC`%)+KpNF%ZPQ03iL}* z0M~T^2AZ9CRx;^H_Wj_v_CA!d#Ipx zjRo!Hw4{f0vq5{cpDc)fG3Eo@)lZ~8Tx_lkHcBL$d2+fAdo77xxL$2#NsV6jki}?` zmojH9Bp9-Au%-}TXZpwYd_4ht5AM_qY=HUdU#TzPeZc2=(aZecdBAD^zmD!27+npP ze}FD+0NDKh!{`EjAfXZL2W0+%ACSrWuYO?Z-e^zG`Q32?PZTbl-DnNMljH zGvo3DQ{OJ%CsL>PRpCbj-DhP1&t9*Oi5*Y5bXPdFM|9&MXatkEqXgo+;*_I^U>f!a zKQ!O6Zo+^ zHi-mU7c6@FwzwIX%mL*2I+O&S*;o+&Z!`b9OQl}?x1|EKJxKyzuB}{uK?kqVEdN^{ z>lfgib04x81KodAc?a$U_Cpf5D}-Gx^-wbk2A*zo!m;MpjDUJIzGf0^oSEH-P*=_< za>NEwLEw%OXB0td1dCoT|5>XR6TFre*WYIT|JzcY>0o@O{g~ET`yYAmK#l1!FnEu6 zLL2l@KGecVgqhbyW}}g>SVXr};*v;!U+OqwS?g;?=pE}AzNnwMcO{I_{mrnm_PQ&8 zVm+Pb_VkZbd4QIO**Ffn9GKE4>G6Ob*T^?|viaf1@#6 zaSwuS$~5y1nwR%k^dcv^^J~k*zS)Mjb~`&4tjIL$7iuXLJWhi0HaVPhzOo~n+Pm?3 zFF1^O!FzqUhWu3a*+J*9bv$=dFAS2B?sXX>Jz z_k+fU0xBIz_U5|dA6}62)8Q>Pg&+EaDvw(W{;D$Mrn~d(X-o2Aje>_DXQ!v-f;+!g zs!;FojnAY|sb`iU-Cy!WHq5v!{d}DEQQTAXVU%b3!xDbxo0JxtgrdBUTL9*LTrvOm zR)8_*#4~kL;5{(N@&=3!i5v_w2yE?Cz-KAQ>1OIkp3(H}RD{u8U+t1AlnQi4A9L-q z{H9ZM0V80O+8#ux;3J)$d;s8qSO6T4*7jhg01NmD9zlF`Q;+a{$0h|HM%ILQ`H4}) zUx3{Whjcohno*>9w?CS%?FwCFXtlOK8iMg*;2%#U`e6BbRR1}feZB*B533A+HvEU! zs1J&OE$EN;1iX#I5|}4{xu0!t=c7saF@dQM)VffzupPE+dZ0aVNKd}@_M`L)h`Hf7 znCJzr!A|63I}mp3{BhF`r+{yc4m%)wnDQF1o{U~5w1cGVNA5C}7Lay-R{|+f^ zdRjatD%QMjgbt`o77g%74`-vE-{~JxL3v-bdL`c64)2=v9`SgCJT7w184>ULmP+LU<8KD zc@RxKOg|Yy3a?~L0$!CLMvMbo`z?qfcJ}UJZ{Y4-A?k9ehgwdsB0BepDv$!8gHwRK zhZF!ENCCu-_&|>6+zJwZr&P#)Tq$I9uu`snuhjH^Tq#koQp10*RLXx`DTrHOC1lCJ zgD>kpzS8gmeExq2U*^nz7QQ4>!8_zz^V|F3`)%O4Lb;O_r_hO{P%YCz_rE>L`)b?6 z&9mherVAPO^p^6qlD$uh>PL$RcV|9%!EOH}>1gx5Rm;;OoC)>FPa>7hOw|rG2k4&H z1E#wIhTy7R43XnTOMl7Nd;43uBY^gCg+V)K%X6OhvJXa9w>ONC4P>NX(m$9IKy>cX z=9>=UQ0pTXC?&AL<>vs#2ewQlV`SkNIWR67*kbk%uSi~JA)H>E1T=ki3d9=sf?Z0;cnBu5e)DBz!4|`t%57qkr|F0BMS(<2}X=IP6 zNU}_KaBU6Q>Qb2$;ZlkUS*Fq;-J8jtwPXob3UMu&sIE0ak}a}EcET`b{?9pvB)XOE z_xt^Qzr9|ZnRDix=Q+>k`F!4=^*kp5C_Q0F>1nV-N>3OlJnjhR6LnP%cD`OY3~JT-6UES zje@3CklJk@(ME8qhns1q{$}8l=mns38rmkYwc;bHfZGK)_!}=qyXE=8>#Sosuz}7C z8R$B|ebXQHe%kB!wgb$sC^kG*(z&yIs6#_nVF%6L6dV4Rq_arHP{-bi7)fmSOG)Qn z!49?}W*0WRP0~3I>}ZyWYuN=U)LV>IQcJSsdL=@gGb)B0ye!7f#B>a9tvQ3V30p~)-eWAo&7f9=!gnk{ezkVBPs za>%GKcx}W#p7r?5aEE3^yL@y>ebhD? z%yIBu_1?v|YC`ueF8I}MJ2PK=eE}cwk8^@Pdy>BD;H78BIha1*)12MUI2inlk1cLi z_b<)<+6m^SIec(aw-Y->ig{I*Kn`Xr30XqNFOo#~Rq-b`CQkVpcx+=~KaR@o`suAr zqZBhIZxQB@frkjoHd8GXUxIxS8{ZPh_`;C74lF}k9+jaD!Y z+i5w`cDxf@tx%xSid##G;u+eaL9NlGJzH|{daroLMbd+o-WAl>jmWMc^m>X<4tfLS zD$mdX4eGPThLsT>l#$UYaaN#Rst>Z?*211t76kykzoi6do`Bo$_^ z+D4urSjS6F>IX_3h(Qzxw!pNq!$Iac*~M4^y7Xg9{hQSbi2oo8xXkd^c-yZweqsQ! z3__ZzYf`VmegQE2>8wk)62h)cPUH+T-ozNe4uPSk)AImcNZb(DyL5d6cn=Imscziq za*Y@1C6?#x(bm9%{^_60ITa?&=mkKz-7P!S%839E4%!mRYhYSBaJ^^MeP@S z7=9P;p5XDw8qGOMkKs991z%BwaI|8c zS_+NXMpgP1?lPiEnkt{&UKYNeW6(YncIXON)h#*E3&=l&T%OkK8T_H zYVLU{TC-QF=AQyNYQqOst9xf!d&i4I8tB(UPN)(f>Nf~$li7?pn~FAG-WvSGpOA0U zn7sOwDJo9%iLX45HfK`-M$*88-;vGRibDnH$6R1i0N+5Kc~ycIm$s65Wn@yNPlUd2 zM5S+pzF$P8UxdDYM5TX(en3QJK!ko^L}g%v{*8#r8xi_J5tTs^`oR&E!4di)5tShk z`k@h(q3*{)D25DL&3d@Jvd5_mSTyz(7%JJKj9UT2g$(?&m<0X~jrIh1$D#uGJ$L~^(mr;)2tsF1Q`i$>YrYQ$gYq$O# zx~(Y351EZxkOlchh4&zk2rs2nAWO(ARtDXx&>YoCH9RhSNv02E!5V}`zuJvn&xaUu z^K~)Ek?3xfuzX>Wx^oh4TliJQJn}I4Y7Ptt7vceeLU&dUm^p4Q? ziTGQQ$$D7WU~o1%=`%u8zn%H=z_7?{PWw7T-D;=Lo~dd6Hb3E&6&POkDiZ%whzm44 zAujMoKwKau3~|9h9SHE!B7XsFW36@{uuX&aQNT9yU0}jt1zq~{C(UG=o#1H6TkHg; z2i?*b!~hG%hiC`BZ!s6nV+V*p97p{nsuV~V1rdlMf%~SYB-o+5lf;L7ACVA*NC-nDL?9BP5eadKgu94@Bt$|gA|V5j zkc~*lMI`)%0J1?XiQD0qP>x7=iAbnHB)mo>G$9h&5DA@#gdRCbpCi5gYMXr14lK<5 zS~#ZV90$YuI?I-6ENvB|XvOyjRXc*V%KSwbk zAd9q}W!rI7uIQ5hd8WfVuZXc+Z-tz2B|k!7Qq1N;jGTdo0Ly(4{PM;&L6&=<+9rhZ zs)vF(x88RgeGmy0jQRUW^EFZCQc>pmQRZi(%>AOwA4Hi~Mw!2lGG7yIE)^|goj72( zKQz|LFWUS;w0UK;`TJ<|H8JK=G3NR)=4ab)-C%G`o6<0!bC~}+?c)Ow?W_Jq}}7Oq%$mk#!mVXkQF#SA!9hlW#B*O&sJZbsw=7_isrQB}1OS zKSkP=Bxhil@J-mBp!*b*hz#sF#JyC*FEn z>bL)Z$hgL{{Rc%3o)tE97B(aZ8~!dlChNSi@^x; zp8M@K7V;4lB$$75(T}S)=6D7CMq>`h(B!)Z>`b*Iz|8b{1k6%_i@++~yBHXyfegSV zm0$)Y>C6*183R{hBSfG05X_Uxf4sOHntq~s23qK9 zK|M2(gYyJpK>Fgf7>K@GCoaqrU$`3h4mk@L)8_DxG+zq*&zWJd{l=dAk9h*`FYQi=UaGooilJ`GuevGc zbyGZbQ-X9;;&f9o2A}V}^za~X&R!c47UThZ2U;#Xr~<3`IjpsiKd_f5mmZYfU}!(^ z;FAO&5=!OG&hJ@q90VC8$Ugr26q4e{P^FEyUJ)naH=k2@Qu~J!!a$~Lb)`?VIvR2j zPanq#7K%OzBrqkjEi`~4#2cfpFLLm(u%W52;R#{G8G56zHoh)mw{Eovw(M7ISsb>k z6I&*tS@x@DS)68BXNlqt=HZh70UaFI%+5=GovbX}LYf0&=Co=5=HMK$f>TLAdsr4L zm~n*QJkHGyXt_YZ^^GydIZZ5WCC&Iaz0?eyltP`98l99*os==1l;yf9>vcgSBwhLc zP&jKLM%pGQ6+edtgB`-EKq$OOV(Xsm5PlamzF)(0Kh|@@E9sul8%BR?g3uxIt?7?27o<0HE!gqs9Z~wtrDI$Q%&!<=xLX^HXc! zlFtJ*BJS9*j87v4lVZsMM{EzdpzLt@m0VD+7=+i&K)pmU<~IWm36_3SR!ji)Xc5Ll z22v^7rl{zy4v!=@EzXDnjA*qh(ZpvLRGbFtRzSIGui7bd=Zs&(gbk&H4dsOmm4!Wd zZ4C9JlIBcSfyleqkI=5qCemMBP^^St{zE+PotWLqjJ$gAZl@+x=DzWl|@5AM0e zBILA)AaPS-nPj+8t61ZTRN^wk%L|wd`|Ou2KfT9zmC%bt1XP1qfo1(oSHc}G@bY=uIF;(nfFyMRH~YQdLt6J!Yogkwz6wxR|ESSy8Gdw+ zKu&FV0dV1w?OI{@5Yoe6{L#s+rQIE*d##|vzVqVVOKCBKX~W z&MYE`-(*{c`0E1I5bf3=M35X^ePpFj$|8dFO*TeE+XW2IzCQ<_^IZh*JdhZ3j_TC( zene$BE2wtl;MZ<8)$(1`{@RN^bhE(A;f6kKqc8OV-`m7(js(|~k;f(*zg^8FI1YY?bW z9n-##vrUt;Z7*k=`tVOO-iO z-MJ)oXc0m5=Dnqej0?G3`<4yJFr67Fz;m(SCNnWfru+_6+OVuMK^8Bc8v$5AGd#j`2nI2yU0bwGtO;*O-f zAHp~ns1Cvc1WbMJE1mFVgaDfMKfzs$c z^-F~G8xy;1x3k!8W3iQBu@xV`8B(;>R*c0KB<|JjNyH>c1SDyd-V!Yi6)nCYTI|>5 zYQvXV{JUuJ6;Ue=re{~U&kN>MNs`WwZ^*l!$5F1bx;$@nxeB5@#6zxIn^2p#rd)-y zJdd+ng{wS|t6XJmdEVM`r}KVk_91VEue%N1cN;2m8|rr(Vs{_f=02qDK6IKfMeSbi zOFp(Btwc;^sK7<5Z`$OX3}*QET@K&$r?-$(?eae0y|okhDc-qPmW-YVrl=rCssbOj zWiqsLMH~jf7(WJlR^m?C{`t4;5k8eeOD zw1t_oO^>rpXShSgmyqs5kntg;dpqyDW)hHoogj0afL(k`1{e^0yAZga+&;h^n4Fby zM_3N*L9qx~Eh22a*|!vtdm(r2b7*ER?0AJ)zwehN%TMfivr_2EB7)4#eT;}6x&P*A zsj*`ovn)39`I1>MUUl5~8zViuBHL=d_u!|t$NFNgE)1q|oDSBsY) z-t&e<=<*`MwwvT35$i=}_Xv_V?=3^rT*%$9&tu7Q+da2d3Dr56`y6RVP}>A-G(wy# zc5(GLZ7$UV1^7)@!W}N~@_E|0lsu{Sv2ehJKL;jjk$7>Y`1jWM?EV)ptM}P2Uaq~z zmUlq%hr<70vP>%c;UGB7@{j54TchuIMHdkSZuT)CA}+KkwOH(1dhpN1%MJDzvj}}K ze}R*cwKOD$v;0Et`hDh0mRs*Ztr99;MA&`vRQoOLCFeBo_=wZ_HE}(X%qH>Qv^iG` zYT>@=O1Q%XUOrD7B`>{i0tZqG4zBotQY~N#iIXIHvn3LQSINWB)+4jpS2&ptpjklVHhbbBa${GajFbLdI&Dg4X?#2d#z|{t?fNir{ zW=QjrVtFB_q<^iQd;!vN`A%7<*BEZB9boTa_)P(csXtlYK72mi=M?CaA4Wx zDi*njAa(QkGQ{f(818+6OO~JA^QJ^Gm|^@R(DDGU^y)1*^7nt!rYjXxe16lFaEA-L ze4aK!68L|@|6XnB4TV&;fTwE!S&dwbUppHcIUB#G!203%aC32iXz^3g;vCW9hrl-7 znpylnwD>+Zx>oGsAv3j#y$Yc>g?B?^Oi0liPwfojC0mkS6=c#1}RhKNc+;eNC zP}U;CuA35!h|UYSq3%1GhwH(UI>14>lTpD(O!%hFs}rD-@tdxMJ6z!9^R%(+pxPxZ zqhXLvAM<%l8*Y-$=VKe~P+5#W(znAE?X=0KvAeVdq|&_x1xwK-z%wrjuO4V09>TpE z0Lx>%haC0_a_!Zn)R$pYSyz(uD5EPrg|$N~m6Du8TDzAcACDvhH-AX|lC&pjYc9t= z^To>{FYv8bnAf7AJoPurQ_+=TRymv>+(3K+^0BoqW<7;@4$bVn$4QlA?L@W?lIo!~vZP?es*3mON=InK2_z8W}aF7!DtCu4yXA5(Q zH;9B#`?JQWHyqv*96+=YczMCqcsapj39e)b#$<^R!1?K2+pCf!@@!!25@E3w9v|W^ z%&jZP4JpVyRG=WFl2lZepBs{&3lKdd{}zrvXToxaLPz2Y``^QGG@EN)Tjg1^h7JYR z*5T#OZ~ZNd<#zkU0BB2XF0(ZrI*rGEeM5Do(0Az6$s_0KUkGKkNQAuII_NY) zrLOsq+>4^od=!+jHNhOF-R@-+Y^1 z1byFV-$6ezME?ogi-OSkp6&46#m78<>+p7Bsb8W6)t#I4w%eE7zTL3TuRqZf)HR#{;?Kb>DmtM*fgHy zh3raO-ti$ov#CD?A;F}g$(=9{h>RmwxIGh9a;6y@gg3&L>E9!4xk+4#NWFmJ+Bdp* zx$z$373O2u`J)>y&rUZa;A{~=F012uhAgiEV_{561sI+aLAQZ>?D9-IwNsPSCImu( zI9p$UKZ!Dh`|CTZxJ~2dJpFVUxDZ7DzUbu7x1HHt#SP5hZ@iID+wMsPs;=EZpmle1L5``?!gD|;&bkS%3e}S zCPPC_ctdCvoZ4|96Q+#WiA6OI{R_+W5H{O!gou37r|kZ0+A z1aGEpM4J`C9trSodCJwckdOu9TAkXd)Q=)}xrlvt7B8POlS3>1U#uYTXPA&Wo~U+d zP@{UZm&57Z>{;M?+Pc75m@9C!TzAi{iXm=KBye<04UA`$$>0NZq|5i)>v*98TO%{ zb>-dgF$lbB5a??hg2Q!f^?wk|<0hLTc!AO_B-1;|93Di?j!h;47LB$SS_X%&2zN@QE zp>HjZ-YG$~Z*5ATU6U)1jF3vnBpsNI%cQ!&~8vOw0hf^xlOpo38Z$<>h!c; z%}DfcKR<2DK=*RR!HsFq4^KM{=_!b zM49V@OZ@K6jMD>}LFta~5~p{zJ^St-n{`vyDzOGVzy8#}#+BjMAs<0E4 zp-zmYAVZMtY6i+v8*-9CPOP`F8a~r1zNbajK6Uo#TB+bdP?q{dCx5=}q-S=0zkTzK zeA;%w&W4;FakK9Fb?ts=o~XZEfKYzXlK*&=Z>7bSf^t=mrui?-+KWDdvi27){>a+@ zO*OzjXz%==k%a%&M;U;F94JxksRB=t@u#oHSfs8iYN}<~)-(aT5Ae5JWsHU7R9Ee|X{WGyBsou;7 zL4-EzO`u$Re=V5;aDG zo@6qqH9mw_e$My{Z=#j18{dWcvt$=i!HqqZLM%j+S5(Kuo0cP2LhlRsTG}wOoVIK) zFgl$Pkt~w@n{Z*beps?ibP_+dEGgRDLN_H#*suv9X6xy{Te%2c0`UQ~@Iyy)|%C&JI1{{3n5w+BC7|JB{jZV!GU`lGvR zG%@9H(rB0%+VVDhd}9rMOg*vJWo+p%sD&EFUc_ynT+8WIxQA06ACMZa*J?dYZ6r5@ z%MU8LqPLG8lbsOsr^M!j%ij~s3COGNbrIWe;fv%t&YhCXcr2R$3UvkEyNl9N^vd~PJ30SBH3*Gy{YKJ0yHTp)djaEP3J zeHT|Yv81axayyGfF=lx(5v;)d!qg|l6lDrOco|!W2}+95^(73Mpri~`!Qvw7Nou>3 zi0}gG>hx9Ix_Q^I8-itC<%A6*5MsKX{vygnei8f{Wl2%y9Xcsa!iKpBv16V#j(%n_ zA+c6!x+#AM8`dDitUdjADHnxAL}`}Uz)QHD0MF~ELk}oHYnjg@1GAZN)`{=}I$tAG zv)lWWxxY?E;r8GKH9j5&yn5R&US41s3q|#*MMe5q(uWC$#xQrC(7OmMI90RZ(>gZ@ zlE+a5168neLT8d%O%ic(bwfQ<6t=oTxDazbi5Mgr3D#@nujb=1P=%j?je?h|e?E5Z zzNoNa5JF7L(_cur$TuQNz04-cyg(<#O4u+1A%^qxmryPWjHokemDvIoc_~MT%^?zC zjTa+mRgsg>(?wr|OS5SR&k4Nh!MEYud^9Bf@zw3YPc(l#3}|^51<7tjmnF1=<1%Lt zQQ?L3o{G2BaM^UQqz_Eo+_lvS6F zf`2_j?~5fif1{G}&dFPg_1Gt3^Y-v{yD=Ro1S7uYIvAmb^4%^C<$;~5NZsp$Zt4fX zdxqd^0a-y0hd>Y!8kTbi7%+U#I@jerz{^UYflS^B_*Fg>+68@qAu?)T$HKQ}EW+yx z7F9pc5}K{2^!9dv26%yp$AAv>PNM5sE6ek;3jNb zQP%K}Fx4n4k1{9gq+Av@1j~hnpKtk`L>Yy>vMefEjUmTuyw#~`6b3PJfFKZ)mewmi z5Agr5a0Aw<7(0?>`t)ptd|yV#))rxd{2fU|K@(JI-o>gBeinzFV4@-kL*^(cL)EJQ zhEvpbrx2I(*w>%o(z!_(GSiAtV6iC06ebgUx_;6#^N}<|Jv9gnzPr!U_N{Y;e9CeI ze}ov;(_c_o!#4tC9KLinZY9b z95a?2qZ>|0F+;T)sM-y0ic3}tN+vdm7K09-$_!<9Y&0Z@YIyn!C>Qxe@T+TkZjSL2 z@e@1X`H9Xi#2&aCF~6!w@925Z8odRKb>CS*>BGSCO7Bny!5-|0No(|Y8gh6eGi<_K z?^`;IJp*`_NON}No4r51( zI>6z@c#9Ysc%#`$>R*l4-KYsrfee*)*PUrCbsbS1#UuvzaD6nGvR-+wTW?4F{I~c` z+&W(5U+BM8xhNole{b3L=(nZsw^1+_YPz^vb1BZcGfx>$X8eraqHp(SM-4Dx{_HT^ zU;ie0oZnKqp0aDq-6>`~t(~!UT(({u@K%@!jWdqV;pd$NtCNWB&G0;#9YYE#8>)J7 z>f9!zn6pMG1xG5f#6G`Rg|$O1HB`k9pXp2{4vR+0v2>JdR64s;&6ZOqgpl5`JZhKw zYw3E$cp^f@+(M^%AkzGbj&DgsEnkPK$yRWH+tGXK&c0QS^MegTFI4`NHp+#=E) z9gI|`1_t+#+26gBtzQCo858DBklW1SfZ-f)>hf|PJR{mt{VGrGI%oN9!V7bhSdFVt znLx*Lj7SP`nONi=mX6XeO=r&XFoLfcs$K>#ii1x+x*Th98+@+9xvxVhlR1CLz<-v#bs-5GwMJfaqES_VQrx2-K z9WTsK-3F=-oaLd=YzkPEU_6s001G49pN~g7ny*O`{Svb>YHyiMwE4&8eR@Y>`0-fX zYGB|4)IE?tzwX%84@viywR1|hc%;}o9CfDgeFe*Zgz^9~zREh#%;2oMbaC<@8IL!# z82}hwIVc+6)92;@yfpjAaz$~nS;k?el{~K{O5xK54ZycMUwHE8+b$SzbNKl~%b!2_ zlNY<3qvRbw&kb6Su$%z;zTWXo@5g{}PRq zVCg8@sC0Oz+CfeocnN)CoMlNz2t)I*48WuUA4OT~-2ZzDph^e5_I_M$fzl*HQ>A|Q1lYno*Arp8lq;GR(KZJ

N(|&TDRU>wnJN0*kZ% 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

`eWemJy>cVj02w+%Ac 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

xvm{lPa0N33WkmmFJGfDyZLow*1W0g+Hh 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 KoxqzJ#Qy;(D;=f) literal 0 HcmV?d00001 diff --git a/source/applications/periodic/CMakeLists.txt b/source/applications/periodic/CMakeLists.txt index 3fca096..7ed44c9 100644 --- a/source/applications/periodic/CMakeLists.txt +++ b/source/applications/periodic/CMakeLists.txt @@ -1,16 +1,16 @@ -cmake_minimum_required(VERSION 3.16) - -project(periodic - VERSION "0.1.0" - LANGUAGES CXX -) - - -add_executable(periodic - periodic.cpp -) - -target_link_libraries(periodic - hpr::csg -) - +cmake_minimum_required(VERSION 3.16) + +project(periodic + VERSION "0.1.0" + LANGUAGES CXX +) + + +add_executable(periodic + periodic.cpp +) + +target_link_libraries(periodic + hpr::csg +) + diff --git a/source/applications/periodic/lattice.hpp b/source/applications/periodic/lattice.hpp index efdeb1d..9db3eb2 100644 --- a/source/applications/periodic/lattice.hpp +++ b/source/applications/periodic/lattice.hpp @@ -1,205 +1,205 @@ -#pragma once - -#include "../../hpr/math.hpp" -#include "../../hpr/csg.hpp" - - -namespace hpr::csg -{ -void prints(scalar point) -{ - std::cout << point << std::endl; -} -class Lattice : public csg::Shape -{ - -public: - - enum class System - { - Triclinic, - Monoclinic, - Orthorhombic, - Tetragonal, - Rhombohedral, - Hexagonal, - Cubic, - Unknown - }; - - enum class Type - { - Primitive, - BaseCentered, - BodyCentered, - FaceCentered, - Unknown - }; - -protected: - - vec3 p_lengths; - vec3 p_angles; - Type p_type; - scalar p_radius; - darray p_controlPoints; - -public: - - Lattice() = delete; - - Lattice(const vec3& lengths, const vec3& angles, scalar radius, Type type) : - csg::Shape {}, - p_lengths {lengths}, - p_angles {angles}, - p_radius {radius}, - p_type {type} - { - generateControlPoints(); - darray spheres; - for (const auto& point : controlPoints()) { - spheres.push(csg::sphere(point, p_radius)); - print(point); - } - - p_shape = csg::Compound(spheres).tshape();//csg::fuse({spheres.front()}, spheres.slice(spheres.begin() + 1, spheres.end())).tshape(); - } - - darray controlPoints() const - { - return p_controlPoints; - } - - vec3 lengths() const - { - return p_lengths; - } - - vec3 angles() const - { - return p_angles; - } - - void generateControlPoints() - { - if (p_type == Type::Unknown) - throw std::runtime_error("Unknown type of lattice"); - p_controlPoints.resize(14); - // - vec3 ox {1, 0, 0}; - vec3 oy {0, 1, 0}; - vec3 oz {0, 0, 1}; - vec3 ox1 = hpr::rotate(ox, oz, radians(-p_angles[2])); - p_controlPoints.push(vec3{0, 0, 0}); - p_controlPoints.push(vec3{0, p_lengths[0], 0}); - vec3 t1 = hpr::translate(p_controlPoints.back(), ox1 * p_lengths[1]); - p_controlPoints.push(t1); - p_controlPoints.push(hpr::translate(p_controlPoints.front(), ox1 * p_lengths[1])); - print(t1); - print(ox1); - scalar c1 = cos(radians(p_angles[2])), c2 = cos(radians(p_angles[1])), c3 = cos(radians(p_angles[0])); - scalar D1 = sqrt(mat3( - 1, cos(radians(p_angles[2])), cos(radians(p_angles[1])), - cos(radians(p_angles[2])), 1, cos(radians(p_angles[0])), - cos(radians(p_angles[1])), cos(radians(p_angles[0])), 1).det()); - scalar volume = 1. / 6. * p_lengths[0] * p_lengths[1] * p_lengths[2] * - D1; - scalar s1 = sqrt(std::pow(p_lengths[0], 2) + std::pow(p_lengths[1], 2) - 2 * - p_lengths[0] * p_lengths[1] * cos(radians(p_angles[2]))); - scalar s2 = sqrt(std::pow(p_lengths[1], 2) + std::pow(p_lengths[2], 2) - 2 * - p_lengths[1] * p_lengths[2] * cos(radians(p_angles[1]))); - scalar s3 = sqrt(std::pow(p_lengths[0], 2) + std::pow(p_lengths[2], 2) - 2 * - p_lengths[0] * p_lengths[2] * cos(radians(p_angles[0]))); - scalar area = 1. / 2. * p_lengths[0] * p_lengths[1] * - sqrt(mat2{1, cos(radians(p_angles[2])), cos(radians(p_angles[2])), 1}.det()); - scalar h1 = 3 * volume / area; - scalar a1 = asin(h1 / p_lengths[2]); - scalar sh1 = sqrt(std::pow(p_lengths[2], 2) - std::pow(h1, 2)); - scalar sh2 = p_lengths[2] * cos(radians(p_angles[0])); - scalar a2 = acos(sh2 / sh1); - - vec3 ox2 = hpr::rotate(ox, oy, a1); - if (!std::isnan(a2)) - ox2 = hpr::rotate(ox2, oz, a2); - print(ox2); - for (auto n = 0; n < 4; ++n) - p_controlPoints.push(hpr::translate(p_controlPoints[n], ox2 * p_lengths[2])); - - /*p_controlPoints.push(vec3{p_lengths[0], p_lengths[1], 0}); - p_controlPoints.push(vec3{p_lengths[0], 0, 0}); - - p_controlPoints.push(vec3{0, 0, p_lengths[2]}); - p_controlPoints.push(vec3{0, p_lengths[1], p_lengths[2]}); - p_controlPoints.push(vec3{p_lengths[0], p_lengths[1], p_lengths[2]}); - p_controlPoints.push(vec3{p_lengths[0], 0, p_lengths[2]}); - - // central points on base faces - if (p_type == Type::BaseCentered || p_type == Type::FaceCentered) - { - for (int n = 0; n < 2; ++n) - { - vec3 center; - for (int k = 0; k < 4; ++k) - center += p_controlPoints[k + 4 * n]; - p_controlPoints.push(center * 0.25); - } - } - - // central point (center of mass) - if (p_type == Type::BodyCentered) - { - vec3 center; - for (const auto& point : p_controlPoints) - center += point; - p_controlPoints.push(center / p_controlPoints.size()); - } - - // central points on side faces - if (p_type == Type::FaceCentered) - { - for (int n = 0; n < 3; ++n) - { - vec3 center; - for (int k = 0; k < 2; ++k) - { - center += p_controlPoints[n + k]; - center += p_controlPoints[n + k + 4]; - } - p_controlPoints.push(center * 0.25); - } - vec3 center; - for (int n = 0; n < 2; ++n) - { - center += p_controlPoints[n * 3]; - center += p_controlPoints[4 + n * 3]; - } - p_controlPoints.push(center * 0.25); - } - - mat4 trans = mat4::identity(); - vec3 ox {1, 0, 0}; - vec3 oy {0, 1, 0}; - vec3 oz {0, 0, 1}; - int n = 0; - for (auto& point : p_controlPoints) - { - if (n == 0 || n == 3) - { - ++n; - continue; - } - trans.row(3, vec4(point, 0)); - trans = hpr::rotate(trans, oz, -radians(90 - p_angles[2])); - if (n >= 4 && n <= 7) - { - trans = hpr::rotate(trans, ox, -radians(90 - p_angles[1])); - trans = hpr::rotate(trans, oy, -radians(90 - p_angles[0])); - } - point = vec3(trans.row(3)[0], trans.row(3)[1], trans.row(3)[2]); - ++n; - }*/ - - } -}; - +#pragma once + +#include "../../hpr/math.hpp" +#include "../../hpr/csg.hpp" + + +namespace hpr::csg +{ +void prints(scalar point) +{ + std::cout << point << std::endl; +} +class Lattice : public csg::Shape +{ + +public: + + enum class System + { + Triclinic, + Monoclinic, + Orthorhombic, + Tetragonal, + Rhombohedral, + Hexagonal, + Cubic, + Unknown + }; + + enum class Type + { + Primitive, + BaseCentered, + BodyCentered, + FaceCentered, + Unknown + }; + +protected: + + vec3 p_lengths; + vec3 p_angles; + Type p_type; + scalar p_radius; + darray p_controlPoints; + +public: + + Lattice() = delete; + + Lattice(const vec3& lengths, const vec3& angles, scalar radius, Type type) : + csg::Shape {}, + p_lengths {lengths}, + p_angles {angles}, + p_radius {radius}, + p_type {type} + { + generateControlPoints(); + darray spheres; + for (const auto& point : controlPoints()) { + spheres.push(csg::sphere(point, p_radius)); + print(point); + } + + p_shape = csg::Compound(spheres).tshape();//csg::fuse({spheres.front()}, spheres.slice(spheres.begin() + 1, spheres.end())).tshape(); + } + + darray controlPoints() const + { + return p_controlPoints; + } + + vec3 lengths() const + { + return p_lengths; + } + + vec3 angles() const + { + return p_angles; + } + + void generateControlPoints() + { + if (p_type == Type::Unknown) + throw std::runtime_error("Unknown type of lattice"); + p_controlPoints.resize(14); + // + vec3 ox {1, 0, 0}; + vec3 oy {0, 1, 0}; + vec3 oz {0, 0, 1}; + vec3 ox1 = hpr::rotate(ox, oz, rad(-p_angles[2])); + p_controlPoints.push(vec3{0, 0, 0}); + p_controlPoints.push(vec3{0, p_lengths[0], 0}); + vec3 t1 = hpr::translate(p_controlPoints.back(), ox1 * p_lengths[1]); + p_controlPoints.push(t1); + p_controlPoints.push(hpr::translate(p_controlPoints.front(), ox1 * p_lengths[1])); + print(t1); + print(ox1); + scalar c1 = cos(rad(p_angles[2])), c2 = cos(rad(p_angles[1])), c3 = cos(rad(p_angles[0])); + scalar D1 = sqrt(det(mat3( + 1, cos(rad(p_angles[2])), cos(rad(p_angles[1])), + cos(rad(p_angles[2])), 1, cos(rad(p_angles[0])), + cos(rad(p_angles[1])), cos(rad(p_angles[0])), 1))); + scalar volume = 1. / 6. * p_lengths[0] * p_lengths[1] * p_lengths[2] * + D1; + scalar s1 = sqrt(pow(p_lengths[0], 2) + pow(p_lengths[1], 2) - 2 * + p_lengths[0] * p_lengths[1] * cos(rad(p_angles[2]))); + scalar s2 = sqrt(pow(p_lengths[1], 2) + pow(p_lengths[2], 2) - 2 * + p_lengths[1] * p_lengths[2] * cos(rad(p_angles[1]))); + scalar s3 = sqrt(pow(p_lengths[0], 2) + pow(p_lengths[2], 2) - 2 * + p_lengths[0] * p_lengths[2] * cos(rad(p_angles[0]))); + scalar area = 1. / 2. * p_lengths[0] * p_lengths[1] * + sqrt(det(mat2{1, cos(rad(p_angles[2])), cos(rad(p_angles[2])), 1})); + scalar h1 = 3 * volume / area; + scalar a1 = asin(h1 / p_lengths[2]); + scalar sh1 = sqrt(pow(p_lengths[2], 2) - pow(h1, 2)); + scalar sh2 = p_lengths[2] * cos(rad(p_angles[0])); + scalar a2 = acos(sh2 / sh1); + + vec3 ox2 = hpr::rotate(ox, oy, a1); + if (!isnan(a2)) + ox2 = hpr::rotate(ox2, oz, a2); + print(ox2); + for (auto n = 0; n < 4; ++n) + p_controlPoints.push(hpr::translate(p_controlPoints[n], ox2 * p_lengths[2])); + + /*p_controlPoints.push(vec3{p_lengths[0], p_lengths[1], 0}); + p_controlPoints.push(vec3{p_lengths[0], 0, 0}); + + p_controlPoints.push(vec3{0, 0, p_lengths[2]}); + p_controlPoints.push(vec3{0, p_lengths[1], p_lengths[2]}); + p_controlPoints.push(vec3{p_lengths[0], p_lengths[1], p_lengths[2]}); + p_controlPoints.push(vec3{p_lengths[0], 0, p_lengths[2]}); + + // central points on base faces + if (p_type == Type::BaseCentered || p_type == Type::FaceCentered) + { + for (int n = 0; n < 2; ++n) + { + vec3 center; + for (int k = 0; k < 4; ++k) + center += p_controlPoints[k + 4 * n]; + p_controlPoints.push(center * 0.25); + } + } + + // central point (center of mass) + if (p_type == Type::BodyCentered) + { + vec3 center; + for (const auto& point : p_controlPoints) + center += point; + p_controlPoints.push(center / p_controlPoints.size()); + } + + // central points on side faces + if (p_type == Type::FaceCentered) + { + for (int n = 0; n < 3; ++n) + { + vec3 center; + for (int k = 0; k < 2; ++k) + { + center += p_controlPoints[n + k]; + center += p_controlPoints[n + k + 4]; + } + p_controlPoints.push(center * 0.25); + } + vec3 center; + for (int n = 0; n < 2; ++n) + { + center += p_controlPoints[n * 3]; + center += p_controlPoints[4 + n * 3]; + } + p_controlPoints.push(center * 0.25); + } + + mat4 trans = mat4::identity(); + vec3 ox {1, 0, 0}; + vec3 oy {0, 1, 0}; + vec3 oz {0, 0, 1}; + int n = 0; + for (auto& point : p_controlPoints) + { + if (n == 0 || n == 3) + { + ++n; + continue; + } + trans.row(3, vec4(point, 0)); + trans = hpr::rotate(trans, oz, -radians(90 - p_angles[2])); + if (n >= 4 && n <= 7) + { + trans = hpr::rotate(trans, ox, -radians(90 - p_angles[1])); + trans = hpr::rotate(trans, oy, -radians(90 - p_angles[0])); + } + point = vec3(trans.row(3)[0], trans.row(3)[1], trans.row(3)[2]); + ++n; + }*/ + + } +}; + } \ No newline at end of file diff --git a/source/applications/periodic/periodic.cpp b/source/applications/periodic/periodic.cpp index 9285fab..c48155e 100644 --- a/source/applications/periodic/periodic.cpp +++ b/source/applications/periodic/periodic.cpp @@ -1,244 +1,244 @@ -#include "../../hpr/math.hpp" -#include "../../hpr/csg.hpp" - -#include -#include - -using namespace hpr; - -void print(vec3 vs) -{ - for (auto& v : vs) - std::cout << v << " "; - std::cout << std::endl; -} - -class Periodic -{ -protected: - scalar p_alpha; - scalar p_initialRadius; - scalar p_sideLength; - scalar p_filletScale; - vec3 p_direction; - -public: - Periodic() : - p_alpha {0.1}, - p_initialRadius {1}, - p_filletScale {0.8}, - p_direction {} - {} - - Periodic(scalar alpha, scalar initialRadius, scalar filletScale, const vec3& direction) : - p_alpha {alpha}, - p_initialRadius {initialRadius}, - p_filletScale {filletScale}, - p_direction {direction} - {} - - virtual - ~Periodic() = default; - - scalar& alpha() - { - return p_alpha; - } - - scalar& initialRadius() - { - return p_initialRadius; - } - - virtual - scalar sideLength() = 0; - - virtual - scalar gamma() = 0; - - scalar& filletScale() - { - return p_filletScale; - } - - scalar radius() const - { - return p_initialRadius / (1. - p_alpha); - } - - scalar filletRadius() - { - scalar analytical = p_initialRadius * sqrt(2) / sqrt(1 - cos(gamma())) - radius(); - return analytical * p_filletScale; - } - - vec3& direction() - { - return p_direction; - } - - virtual - void build() = 0; -}; - -class Simple : public Periodic, public csg::Shape -{ -public: - - Simple() : - csg::Shape {}, - Periodic {0.01, 1, 0.8, vec3(1., 0., 0.)} - {} - - Simple(scalar alpha, const vec3& direction, scalar filletScale = 0.8) : - Simple {} - { - p_alpha = alpha; - p_direction = direction; - p_filletScale = filletScale; - } - - Simple(scalar alpha, scalar initialRadius, scalar filletScale, const vec3& direction) : - Periodic {alpha, initialRadius, filletScale, direction} - {} - - ~Simple() override = default; - - scalar sideLength() override - { - return 2 * initialRadius(); - } - - scalar gamma() override - { - return hpr::PI - 2 * 0.5 * 0.5 * hpr::PI; - } - - csg::Shape lattice() - { - csg::Shape lattice; - darray spheres; - - for (int zn = 0; zn < 3; ++zn) - { - scalar z = zn * sideLength(); - for (int yn = 0; yn < 3; ++yn) - { - scalar y = yn * sideLength(); - for (int xn = 0; xn < 3; ++xn) - { - scalar x = xn * sideLength(); - spheres.push(csg::sphere(vec3(x, y, z), radius())); - } - } - } - - lattice = csg::fuse({spheres.front()}, spheres.slice(spheres.begin() + 1, spheres.end())); - - if (filletScale() > 0) - { - lattice = lattice.scale({0, 0, 0}, 1e+2); - lattice = lattice.fillet(lattice.edges(), filletRadius() * 1e+2); - lattice = lattice.scale({0, 0, 0}, 1e-2); - } - - std::cout << (int)lattice.type() << std::endl; - return lattice; - } - - csg::Shape boxCell() - { - scalar length = sideLength() * sqrt(2); - scalar width = sideLength() * sqrt(2); - scalar height = sideLength(); - scalar xl = sqrt(pow(length, 2) * 0.5); - scalar yw = xl; - scalar zh = height; - darray edges { - csg::Edge({xl, 0, 0}, {0, yw, 0}), - csg::Edge({0, yw, 0}, {0, yw, zh}), - csg::Edge({0, yw, zh}, {xl, 0, zh}), - csg::Edge({xl, 0, zh}, {xl, 0, 0}) - }; - csg::Face plgm {edges}; - - vec3 localX {csg::Surface(plgm).normal(0, 0)}; - vec3 localZ = vec3(0, 0, 1); - vec3 localY = cross(localX, localZ); - csg::Shape cell = plgm.extrude(localX, width); - - scalar angle; - hpr::vec3 normal; - - for (auto& face : cell.faces()) - { - normal = csg::Surface(csg::Face(face)).normal(0, 0); - angle = hpr::angle(localX, normal); - - if (face.tshape().Orientation() == TopAbs_FORWARD) - { - normal = -normal; - angle = hpr::angle(localX, normal); - } - - if (equal(angle, 0.)) - face.label("periodic-south"); - else if (equal(angle, hpr::PI)) - face.label("periodic-north"); - - angle = hpr::angle(localY, normal); - if (equal(angle, 0.)) - face.label("periodic-east"); - else if (equal(angle, hpr::PI)) - face.label("periodic-west"); - - angle = hpr::angle(localZ, normal); - if (equal(angle, hpr::PI)) - face.label("periodic-down"); - else if (equal(angle, 0.)) - face.label("periodic-up"); - } - - std::cout << (int)cell.type() << std::endl; - return cell; - } - - csg::Shape hexagonalPrismCell() - { - return csg::Shape(); - } - - void build() override - { - if (direction() == vec3(1., 0., 0.) || direction() == vec3(1., 0., 0.) || direction() == vec3(0., 0., 1.)) - p_shape = csg::cut(boxCell(), lattice()).tshape(); - else if (direction() == vec3(1., 1., 1.)) - p_shape = csg::cut(hexagonalPrismCell(), lattice()).tshape(); - else - throw std::runtime_error("Undefined cell for passed direction"); - - - p_shape = this->translate(-this->center()).tshape(); - p_shape = this->rotate(this->center(), {0, 0, 1}, 45).tshape(); - - for (auto& face : faces()) - if (face.label() == "default") - face.label("wall"); - } - -}; - -#include "lattice.hpp" - -int main(int argc, char** argv) -{ - /*Simple simple {0.01, {1., 0., 0.}}; - simple.build(); - - std::cout << (int)simple.type() << std::endl; - std::cout << simple.volume() << std::endl; - */ - csg::Lattice lattice {{2, 2, 2}, {90, 90, 90}, 1, csg::Lattice::Type::Primitive}; - lattice.dump("latticeTest.step", csg::Shape::Format::STEP); - return 0; +#include "../../hpr/math.hpp" +#include "../../hpr/csg.hpp" + +#include +#include + +using namespace hpr; + +void print(vec3 vs) +{ + for (auto& v : vs) + std::cout << v << " "; + std::cout << std::endl; +} + +class Periodic +{ +protected: + scalar p_alpha; + scalar p_initialRadius; + scalar p_sideLength; + scalar p_filletScale; + vec3 p_direction; + +public: + Periodic() : + p_alpha {0.1}, + p_initialRadius {1}, + p_filletScale {0.8}, + p_direction {} + {} + + Periodic(scalar alpha, scalar initialRadius, scalar filletScale, const vec3& direction) : + p_alpha {alpha}, + p_initialRadius {initialRadius}, + p_filletScale {filletScale}, + p_direction {direction} + {} + + virtual + ~Periodic() = default; + + scalar& alpha() + { + return p_alpha; + } + + scalar& initialRadius() + { + return p_initialRadius; + } + + virtual + scalar sideLength() = 0; + + virtual + scalar gamma() = 0; + + scalar& filletScale() + { + return p_filletScale; + } + + scalar radius() const + { + return p_initialRadius / (1. - p_alpha); + } + + scalar filletRadius() + { + scalar analytical = p_initialRadius * sqrt(2) / sqrt(1 - cos(gamma())) - radius(); + return analytical * p_filletScale; + } + + vec3& direction() + { + return p_direction; + } + + virtual + void build() = 0; +}; + +class Simple : public Periodic, public csg::Shape +{ +public: + + Simple() : + csg::Shape {}, + Periodic {0.01, 1, 0.8, vec3(1., 0., 0.)} + {} + + Simple(scalar alpha, const vec3& direction, scalar filletScale = 0.8) : + Simple {} + { + p_alpha = alpha; + p_direction = direction; + p_filletScale = filletScale; + } + + Simple(scalar alpha, scalar initialRadius, scalar filletScale, const vec3& direction) : + Periodic {alpha, initialRadius, filletScale, direction} + {} + + ~Simple() override = default; + + scalar sideLength() override + { + return 2 * initialRadius(); + } + + scalar gamma() override + { + return hpr::PI - 2 * 0.5 * 0.5 * hpr::PI; + } + + csg::Shape lattice() + { + csg::Shape lattice; + darray spheres; + + for (int zn = 0; zn < 3; ++zn) + { + scalar z = zn * sideLength(); + for (int yn = 0; yn < 3; ++yn) + { + scalar y = yn * sideLength(); + for (int xn = 0; xn < 3; ++xn) + { + scalar x = xn * sideLength(); + spheres.push(csg::sphere(vec3(x, y, z), radius())); + } + } + } + + lattice = csg::fuse({spheres.front()}, spheres.slice(spheres.begin() + 1, spheres.end())); + + if (filletScale() > 0) + { + lattice = lattice.scale({0, 0, 0}, 1e+2); + lattice = lattice.fillet(lattice.edges(), filletRadius() * 1e+2); + lattice = lattice.scale({0, 0, 0}, 1e-2); + } + + std::cout << (int)lattice.type() << std::endl; + return lattice; + } + + csg::Shape boxCell() + { + scalar length = sideLength() * sqrt(2); + scalar width = sideLength() * sqrt(2); + scalar height = sideLength(); + scalar xl = sqrt(pow(length, 2) * 0.5); + scalar yw = xl; + scalar zh = height; + darray edges { + csg::Edge({xl, 0, 0}, {0, yw, 0}), + csg::Edge({0, yw, 0}, {0, yw, zh}), + csg::Edge({0, yw, zh}, {xl, 0, zh}), + csg::Edge({xl, 0, zh}, {xl, 0, 0}) + }; + csg::Face plgm {edges}; + + vec3 localX {csg::Surface(plgm).normal(0, 0)}; + vec3 localZ = vec3(0, 0, 1); + vec3 localY = cross(localX, localZ); + csg::Shape cell = plgm.extrude(localX, width); + + scalar angle; + hpr::vec3 normal; + + for (auto& face : cell.faces()) + { + normal = csg::Surface(csg::Face(face)).normal(0, 0); + angle = hpr::angle(localX, normal); + + if (face.tshape().Orientation() == TopAbs_FORWARD) + { + normal = -normal; + angle = hpr::angle(localX, normal); + } + + if (equal(angle, 0.)) + face.label("periodic-south"); + else if (equal(angle, hpr::PI)) + face.label("periodic-north"); + + angle = hpr::angle(localY, normal); + if (equal(angle, 0.)) + face.label("periodic-east"); + else if (equal(angle, hpr::PI)) + face.label("periodic-west"); + + angle = hpr::angle(localZ, normal); + if (equal(angle, hpr::PI)) + face.label("periodic-down"); + else if (equal(angle, 0.)) + face.label("periodic-up"); + } + + std::cout << (int)cell.type() << std::endl; + return cell; + } + + csg::Shape hexagonalPrismCell() + { + return csg::Shape(); + } + + void build() override + { + if (direction() == vec3(1., 0., 0.) || direction() == vec3(1., 0., 0.) || direction() == vec3(0., 0., 1.)) + p_shape = csg::cut(boxCell(), lattice()).tshape(); + else if (direction() == vec3(1., 1., 1.)) + p_shape = csg::cut(hexagonalPrismCell(), lattice()).tshape(); + else + throw std::runtime_error("Undefined cell for passed direction"); + + + p_shape = this->translate(-this->center()).tshape(); + p_shape = this->rotate(this->center(), {0, 0, 1}, 45).tshape(); + + for (auto& face : faces()) + if (face.label() == "default") + face.label("wall"); + } + +}; + +#include "lattice.hpp" + +int main(int argc, char** argv) +{ + /*Simple simple {0.01, {1., 0., 0.}}; + simple.build(); + + std::cout << (int)simple.type() << std::endl; + std::cout << simple.volume() << std::endl; + */ + csg::Lattice lattice {{2, 2, 2}, {90, 90, 90}, 1, csg::Lattice::Type::Primitive}; + lattice.dump("latticeTest.step", csg::Shape::Format::STEP); + return 0; } \ No newline at end of file diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index bbe1c42..0e4cde4 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -1,3 +1,8 @@ +cmake_minimum_required (VERSION 3.16) + +include(${CMAKE_SOURCE_DIR}/cmake/external/imgui.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external/implot.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external/glm.cmake) project( hyporo-creator @@ -7,55 +12,36 @@ project( # Compiler options set(CMAKE_CXX_STANDARD 20) -#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") # Project options -include(GNUInstallDirs) +#include(GNUInstallDirs) + +#include (InstallRequiredSystemLibraries) + +if (MINGW) + list(APPEND MINGW_SYSTEM_RUNTIME_LIBS + "/mingw64/bin/libgcc_s_seh-1.dll" + "/mingw64/bin/libwinpthread-1.dll" + "/mingw64/bin/libstdc++-6.dll" + ) +endif() -#add_executable(${PROJECT_NAME} -# creator.cpp -#) -include(${CMAKE_SOURCE_DIR}/cmake/external/imgui.cmake) -#message(STATUS "project name: ${PROJECT_NAME}") -#target_link_libraries(${PROJECT_NAME} -# hpr::hpr -# imgui -#) -#target_include_directories(${PROJECT_NAME} -# PRIVATE -# ../ -#) +#find_package(hpr REQUIRED) -#set(CMAKE_CXX_STANDARD 20) -#add_executable(testi -# test.cpp -#) - -#target_include_directories(testi -# PRIVATE -# ../ -#) - -#target_link_libraries(testi -# hpr::hpr -# imgui -#) -set(CMAKE_CXX_STANDARD 20) add_executable(hyporo-creator - test2.cpp - ) - -target_include_directories(hyporo-creator - PRIVATE - ../ - ) + test2.cpp +) target_link_libraries(hyporo-creator - hpr::gpu - hpr::window-system - imgui - ) + hpr::gpu + imgui::imgui + implot::implot + glm::glm +) + +target_link_libraries(hyporo-creator -static gcc stdc++ winpthread -dynamic) + diff --git a/source/creator/camera.hpp b/source/creator/camera.hpp new file mode 100644 index 0000000..1c3ca84 --- /dev/null +++ b/source/creator/camera.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include + +class Camera +{ + +public: + + enum Projection + { + Perspective, + Orthographic + }; + +protected: + + Projection p_projectionType; + + hpr::scalar p_distance; + hpr::scalar p_aspect; + hpr::scalar p_fieldOfView; + hpr::scalar p_nearPlane; + hpr::scalar p_farPlane; + + hpr::vec3 p_target; + hpr::vec3 p_left; + hpr::vec3 p_front; + hpr::vec3 p_up; + hpr::vec3 p_angles; + + hpr::vec3 p_position; + +public: + + inline + Camera() : + p_projectionType {Perspective}, + p_distance {10}, + p_aspect {4.f / 3.f}, + p_fieldOfView {45}, + p_nearPlane {0.1},//{-100}, + p_farPlane {100}, + p_target {0, 0, 0}, + p_left {1, 0, 0}, + p_front {0, 0, -1}, + p_up {0, 0, 1}, + p_angles {45, 45, 0}, + p_position {} + {} + + virtual + ~Camera() = default; + + virtual inline + void projection(Projection projection) + { + p_projectionType = projection; + } + + hpr::scalar& aspect() + { + return p_aspect; + } + + virtual inline + hpr::mat4 projection() const + { + switch (p_projectionType) + { + case Perspective: + return hpr::perspective(hpr::rad(p_fieldOfView), p_aspect, p_nearPlane, p_farPlane); + case Orthographic: + return hpr::ortho(-p_distance * p_aspect, p_distance * p_aspect, -p_distance, p_distance, p_nearPlane, p_farPlane); + } + } + + virtual inline + hpr::mat4 view() = 0; + + virtual inline + hpr::vec3& position() + { + return p_position; + } +}; + +class OrbitCamera : public Camera +{ +public: + + inline + OrbitCamera() : + Camera {} + {} + + virtual + ~OrbitCamera() = default; + + inline + hpr::mat4 view() override + { + hpr::vec3 rotation { + hpr::cos(hpr::rad(p_angles[1])) * hpr::sin(hpr::rad(p_angles[0])), + hpr::cos(hpr::rad(p_angles[1])) * hpr::cos(hpr::rad(p_angles[0])), + hpr::sin(hpr::rad(p_angles[1]))}; + hpr::vec3 pos = p_target + p_distance * rotation; + + p_front = p_target - pos; + p_front /= hpr::length(p_front); + + p_up = hpr::vec3(0, 0, 1); + p_left = hpr::normalize(hpr::cross(p_up, p_front)); + p_up = hpr::cross(p_front, p_left); + + p_position = pos; + + return hpr::lookAt(pos, pos + p_front, p_up); + } + + void rotateEvent(float xdelta, float ydelta) + { + p_angles += hpr::vec3(xdelta, ydelta, 0); + } + + void moveEvent(float xdelta, float ydelta) + { + p_target += (p_left * xdelta + p_up * ydelta) * 1e-3f; + } + + void scrollEvent(float xdelta, float ydelta) + { + p_distance -= ydelta; + } +}; \ No newline at end of file diff --git a/source/creator/cmake/mingw-pre-pack.cmake.in b/source/creator/cmake/mingw-pre-pack.cmake.in new file mode 100644 index 0000000..cd434a1 --- /dev/null +++ b/source/creator/cmake/mingw-pre-pack.cmake.in @@ -0,0 +1,12 @@ + +execute_process( + COMMAND ldd "@CPACK_PACKAGE_DIRECTORY@/@PROJECT_NAME@.exe" + COMMAND grep "mingw" + COMMAND cut "-d" " " "-f" "3" + COMMAND xargs "cp" "-t" "@CMAKE_INSTALL_BINDIR@" + RESULT_VARIABLE EXIT_CODE +) + +if(NOT EXIT_CODE EQUAL 0) + message(STATUS "Running pre-build command failed with exit code ${EXIT_CODE}.") +endif() \ No newline at end of file diff --git a/source/creator/drawable.hpp b/source/creator/drawable.hpp new file mode 100644 index 0000000..e03132b --- /dev/null +++ b/source/creator/drawable.hpp @@ -0,0 +1,238 @@ +#pragma once + +#include +#include + +using hpr::darray; + +class Drawable +{ + +protected: + + gpu::ArrayObject p_arrayObject; + + gpu::BufferObject p_vertexBuffer; + gpu::BufferObject p_normalBuffer; + gpu::BufferObject p_colorBuffer; + gpu::BufferObject p_indexBuffer; + + gpu::ShaderProgram* p_shaderProgram; + + gpu::ArrayObject::Mode p_renderMode; + + hpr::vec4 p_color; + +public: + + gpu::ArrayObject& array() + { + return p_arrayObject; + } + friend constexpr void swap(Drawable& main, Drawable& other) + { + using std::swap; + //swap(main.p) + } + + Drawable() : + p_arrayObject {}, + p_vertexBuffer {gpu::BufferObject::Vertex}, + p_normalBuffer {gpu::BufferObject::Vertex}, + p_colorBuffer {gpu::BufferObject::Vertex}, + p_indexBuffer {gpu::BufferObject::Index}, + p_shaderProgram {nullptr}, + p_renderMode {gpu::ArrayObject::Triangles}, + p_color {0.7, 0.7, 0.7} + {} + + inline explicit + Drawable(gpu::ShaderProgram* shaderProgram) : + p_arrayObject {}, + p_vertexBuffer {gpu::BufferObject::Vertex}, + p_normalBuffer {gpu::BufferObject::Vertex}, + p_colorBuffer {gpu::BufferObject::Vertex}, + p_indexBuffer {gpu::BufferObject::Index}, + p_shaderProgram {shaderProgram}, + p_renderMode {gpu::ArrayObject::Triangles}, + p_color {0.7, 0.7, 0.7} + {} + + inline + Drawable(const Drawable& drawable) = default; + + virtual + ~Drawable() = default; + + void destroy() + { + p_arrayObject.destroy(); + p_vertexBuffer.destroy(); + p_normalBuffer.destroy(); + p_colorBuffer.destroy(); + p_indexBuffer.destroy(); + } + + [[nodiscard]] + constexpr + gpu::ShaderProgram* shaderProgram() const + { + return p_shaderProgram; + } + + constexpr virtual + void renderMode(gpu::ArrayObject::Mode mode) + { + p_renderMode = mode; + } + + [[nodiscard]] + constexpr virtual + gpu::ArrayObject::Mode renderMode() const + { + return p_renderMode; + } + + template + inline + void addVertices(const darray& vertices) + { + if (!p_arrayObject.valid()) + p_arrayObject.create(); + p_arrayObject.bind(); + + darray data; + data.resize(vertices.size() * vertices[0].size()); + for (auto v : vertices) for (auto c : v) data.push(c); + p_vertexBuffer.create(data); + + p_arrayObject.attribPointer(p_vertexBuffer, 0, vertices[0].size()); + p_arrayObject.unbind(); + } + + template + inline + void editVertices(const darray& vertices) + { + if (!p_vertexBuffer.valid()) + throw std::runtime_error("Invalid buffer object"); + p_arrayObject.bind(); + + darray data; + data.resize(vertices.size() * vertices[0].size()); + for (auto v : vertices) for (auto c : v) data.push(c); + + p_vertexBuffer.edit(data); + + p_arrayObject.unbind(); + } + + template + inline + void addNormals(const darray& normals) + { + if (!p_arrayObject.valid()) + p_arrayObject.create(); + p_arrayObject.bind(); + + darray data; + data.resize(normals.size() * normals[0].size()); + for (auto v : normals) for (auto c : v) data.push(c); + p_normalBuffer.create(data); + + p_arrayObject.attribPointer(p_normalBuffer, 1, normals[0].size()); + p_arrayObject.unbind(); + } + + template + inline + void addColors(const darray& colors) + { + if (!p_arrayObject.valid()) + p_arrayObject.create(); + p_arrayObject.bind(); + + darray data; + data.resize(colors.size() * colors[0].size()); + for (auto v : colors) for (auto c : v) data.push(c); + p_colorBuffer.create(data); + + p_arrayObject.attribPointer(p_colorBuffer, 2, colors[0].size()); + p_arrayObject.unbind(); + } + + template + inline + void editColors(const darray& colors) + { + if (!p_colorBuffer.valid()) + throw std::runtime_error("Invalid buffer object"); + p_arrayObject.bind(); + + darray data; + data.resize(colors.size() * colors[0].size()); + for (auto v : colors) for (auto c : v) data.push(c); + + p_colorBuffer.edit(data); + + p_arrayObject.unbind(); + } + + template + inline + void addIndices(const darray& indices) + { + if (!p_arrayObject.valid()) + p_arrayObject.create(); + p_arrayObject.bind(); + + darray data; + data.resize(indices.size() * indices[0].size()); + for (auto v : indices) for (auto c : v) data.push(c); + p_indexBuffer.create(data); + + p_arrayObject.unbind(); + } + + inline virtual + void render(gpu::ArrayObject::Mode mode) + { + p_shaderProgram->bind(); + //std::cout << p_shaderProgram->index() << std::endl; + p_arrayObject.bind(); + + if (!p_colorBuffer.valid()) { + + shaderProgram()->uniformVector("objectColor", 1, p_color.data()); + } + if (p_indexBuffer.valid()) + { + p_indexBuffer.bind(); + p_arrayObject.drawElements(mode, p_indexBuffer.size()); + } + else + { + p_arrayObject.drawArrays(mode, p_vertexBuffer.size()); + } + p_arrayObject.unbind(); + p_shaderProgram->unbind(); + } + + inline virtual + void render() + { + render(p_renderMode); + } + + inline virtual + void color(const hpr::vec4& color) + { + p_color = color; + } + + inline virtual + hpr::vec4& color() + { + return p_color; + } +}; diff --git a/source/creator/entity.hpp b/source/creator/entity.hpp new file mode 100644 index 0000000..87f8478 --- /dev/null +++ b/source/creator/entity.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "drawable.hpp" + + +class Entity : public Drawable +{ + +protected: + + hpr::vec3 p_position; + hpr::quat p_rotation; + hpr::vec3 p_scale; + +public: + + Entity() : + Drawable {} + {} + + inline explicit + Entity(gpu::ShaderProgram* shaderProgram) : + Drawable {shaderProgram} + {} + + inline + Entity(const Entity& entity) = default; + /*{ + std::cout << "copy" << std::endl; + }*/ + + ~Entity() override = default; + + inline virtual + void rotation(const hpr::vec3& angles) + { + p_rotation = hpr::quat(hpr::quat::XYZ, angles); + } + + inline virtual + hpr::quat& rotation() + { + return p_rotation; + } + + inline virtual + void rotate(const hpr::vec3& axis, hpr::scalar angle) + { + p_rotation *= hpr::quat(axis, angle); + } + + hpr::mat4 model() + { + hpr::mat4 model = hpr::mat4::identity(); + hpr::translate(model, p_position); + hpr::rotate(model, p_rotation); + hpr::scale(model, p_scale); + return model; + } + + inline + void render() override + { + shaderProgram()->bind(); + //std::cout << shaderProgram()->index() << std::endl; + + //for (auto v : model) + // std::cout << v << " "; + //std::cout << std::endl; + shaderProgram()->uniformMatrix<4, 4>("model", 1, true, model().data()); + + Drawable::render(); + + shaderProgram()->unbind(); + } + +}; \ No newline at end of file diff --git a/source/creator/grid.hpp b/source/creator/grid.hpp new file mode 100644 index 0000000..4366624 --- /dev/null +++ b/source/creator/grid.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include "entity.hpp" + + +class Grid : public Entity +{ + +public: + + enum Mode + { + XY, + XZ, + YZ + }; + +protected: + + hpr::scalar p_length; + hpr::scalar p_width; + hpr::scalar p_height; + hpr::scalar p_scale; + Mode p_mode; + +public: + + + Grid(gpu::ShaderProgram* shaderProgram, Mode mode = XY, hpr::scalar scale = 1, hpr::scalar length = 100, hpr::scalar width = 100, hpr::scalar height = 100) : + Entity {shaderProgram}, + p_length {length}, + p_width {width}, + p_height {height}, + p_scale {scale}, + p_mode {mode} + { + darray vertices; + darray colors; + + construct(vertices, colors); + + Entity::addVertices(vertices); + Entity::addColors(colors); + Entity::renderMode(gpu::ArrayObject::Lines); + } + +protected: + + void construct(darray& vertices, darray& colors) + { + const darray red {vec3(0.98f, 0.21f, 0.32f), vec3(0.98f, 0.21f, 0.32f)}; + const darray green {vec3(0.45f, 0.67f, 0.1f), vec3(0.45f, 0.67f, 0.1f)}; + const darray blue {vec3(0.17f, 0.47f, 0.80f), vec3(0.17f, 0.47f, 0.80f)}; + const darray white {vec3(0.5f, 0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f)}; + + switch (p_mode) { + case XY: { + // Y + vertices.push({vec3(0.f, -p_width, 0.f), vec3(0.f, p_width, 0.f)}); + colors.push(green); + for (hpr::scalar x = p_scale; x <= p_length; ) + { + vertices.push({vec3(x, -p_width, 0.f), vec3(x, p_width, 0.f)}); + colors.push(white); + vertices.push({vec3(-x, -p_width, 0.f), vec3(-x, p_width, 0.f)}); + colors.push(white); + x += p_scale; + } + // X + vertices.push({vec3(-p_length, 0.f, 0.f), vec3(p_length, 0.f, 0.f)}); + colors.push(red); + for (hpr::scalar y = p_scale; y <= p_width; ) + { + vertices.push({vec3(-p_length, y, 0.f), vec3(p_length, y, 0.f)}); + colors.push(white); + vertices.push({vec3(-p_length, -y, 0.f), vec3(p_length, -y, 0.f)}); + colors.push(white); + y += p_scale; + } + break; + } + case XZ: { + // Z + vertices.push({vec3(0.f, 0.f, -p_height), vec3(0.f, 0.f, p_height)}); + colors.push(blue); + for (hpr::scalar x = p_scale; x <= p_length; ) + { + vertices.push({vec3(x, 0.f, -p_height), vec3(x, 0.f, p_height)}); + colors.push(white); + vertices.push({vec3(-x, 0.f, -p_height), vec3(-x, 0.f, p_height)}); + colors.push(white); + x += p_scale; + } + // X + vertices.push({vec3(-p_length, 0.f, 0.f), vec3(p_length, 0.f, 0.f)}); + colors.push(red); + for (hpr::scalar z = p_scale; z <= p_height; ) + { + vertices.push({vec3(-p_length, 0.f, z), vec3(p_length, 0.f, z)}); + colors.push(white); + vertices.push({vec3(-p_length, 0.f, -z), vec3(p_length, 0.f, -z)}); + colors.push(white); + z += p_scale; + } + break; + } + case YZ: { + // Y + vertices.push({vec3(0.f, -p_width, 0.f), vec3(0.f, p_width, 0.f)}); + colors.push(green); + for (hpr::scalar z = p_scale; z <= p_height; ) + { + vertices.push({vec3(0.f, -p_width, z), vec3(0.f, p_width, z)}); + colors.push(white); + vertices.push({vec3(0.f, -p_width, -z), vec3(0.f, p_width, -z)}); + colors.push(white); + z += p_scale; + } + // Z + vertices.push({vec3(0.f, 0.f, -p_height), vec3(0.f, 0.f, p_height)}); + colors.push(blue); + for (hpr::scalar y = p_scale; y <= p_width; ) + { + vertices.push({vec3(0.f, y, -p_height), vec3(0.f, y, p_height)}); + colors.push(white); + vertices.push({vec3(0.f, -y, -p_height), vec3(0.f, -y, p_height)}); + colors.push(white); + y += p_scale; + } + } + } + } + +public: + + void rebuild() + { + darray vertices; + darray colors; + + construct(vertices, colors); + + Entity::editVertices(vertices); + Entity::editColors(colors); + Entity::renderMode(gpu::ArrayObject::Lines); + } + + void mode(Mode mode) + { + p_mode = mode; + } + + void scale(hpr::scalar scale) + { + p_scale = scale; + } +}; \ No newline at end of file diff --git a/source/creator/ray.hpp b/source/creator/ray.hpp new file mode 100644 index 0000000..857204a --- /dev/null +++ b/source/creator/ray.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include +#include "camera.hpp" +#include "entity.hpp" + +using namespace hpr; + +vec3 unproject(const vec3& win, const mat4& model, const mat4& proj, const vec4& viewport) +{ + mat4 inverse = inv(proj * model); + vec4 tmp = vec4(win, 1.); + tmp[0] = (tmp[0] - viewport[0]) / viewport[2]; + tmp[1] = (tmp[1] - viewport[1]) / viewport[3]; + tmp = tmp * 2.f - 1.f; + vec4 obj = inverse * tmp; + obj /= obj[3]; + return vec3(obj); +} + +class Ray +{ +public: + vec3 p_position; + vec3 p_direction; + + + Ray() : + p_position {}, + p_direction {} + {} + + Ray(const vec3& position, const vec3& direction) : + p_position {position}, + p_direction {direction} + {} + + void fromScreen(int x, int y, int width, int height, Camera* camera, Entity* entity) + { + /*vec4 start {2.f * (float)x / (float)width - 1.f, 2.f * (float)y / (float)height - 1.f, 0.f, 1.f}; + vec4 end {2.f * (float)x / (float)width - 1.f, 2.f * (float)y / (float)height - 1.f, 1.f, 1.f}; + + mat4 invPV = inv(transpose(camera->projection()) * transpose(camera->view())); + vec4 startWorld = invPV * start; + startWorld /= startWorld[3]; + vec4 endWorld = invPV * end; + endWorld /= endWorld[3];*/ + + + vec3 end = unproject(vec3(x, y, 0), entity->model(), camera->projection(), vec4(0, 0, width, height)); + + p_position = vec3(startWorld); + p_direction = normalize(vec3(endWorld - startWorld)); + } + + bool obb(const vec3& aabbMin, const vec3& aabbMax, Entity* entity, float& tMin, float& tMax) + { + tMin = 0.; + tMax = 1e+5; + + mat4 model = entity->model(); + vec3 obbPos {model(0, 3), model(1, 3), model(2, 3)}; + vec3 delta = obbPos - p_position; + + for (auto n = 0; n < 3; ++n) + { + vec3 axis {model(0, n), model(1, n), model(2, n)}; + scalar e = dot(axis, delta); + scalar f = dot(p_direction, axis); + + if (hpr::abs(f) > 1e-3) + { + scalar t1 = (e + aabbMin[n]) / f; + scalar t2 = (e + aabbMax[n]) / f; + + if (t1 > t2) + std::swap(t1, t2); + if (t2 < tMax) + tMax = t2; + if (t1 > tMin) + tMin = t1; + if (tMax < tMin) + return false; + } + else + { + if (-e + aabbMin[n] > 0.f || -e + aabbMax[n] < 0.f) + return false; + } + } + //dist = tMin; + return true; + } +}; + + +/* +bool RayIntersectsTriangle(Vector3D rayOrigin, + Vector3D rayVector, + Triangle* inTriangle, + Vector3D& outIntersectionPoint) +{ + const float EPSILON = 0.0000001; + Vector3D vertex0 = inTriangle->vertex0; + Vector3D vertex1 = inTriangle->vertex1; + Vector3D vertex2 = inTriangle->vertex2; + Vector3D edge1, edge2, h, s, q; + float a,f,u,v; + edge1 = vertex1 - vertex0; + edge2 = vertex2 - vertex0; + h = rayVector.crossProduct(edge2); + a = edge1.dotProduct(h); + if (a > -EPSILON && a < EPSILON) + return false; // This ray is parallel to this triangle. + f = 1.0/a; + s = rayOrigin - vertex0; + u = f * s.dotProduct(h); + if (u < 0.0 || u > 1.0) + return false; + q = s.crossProduct(edge1); + v = f * rayVector.dotProduct(q); + if (v < 0.0 || u + v > 1.0) + return false; + // At this stage we can compute t to find out where the intersection point is on the line. + float t = f * edge2.dotProduct(q); + if (t > EPSILON) // ray intersection + { + outIntersectionPoint = rayOrigin + rayVector * t; + return true; + } + else // This means that there is a line intersection but not a ray intersection. + return false; +} + */ \ No newline at end of file diff --git a/source/creator/scene.hpp b/source/creator/scene.hpp new file mode 100644 index 0000000..c66afd2 --- /dev/null +++ b/source/creator/scene.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "camera.hpp" +#include "entity.hpp" + +#include + + +class Scene +{ + +protected: + + Camera* p_camera; + hpr::darray> p_nodes; + +public: + + inline + Scene() + {} + + virtual + ~Scene() + { + delete p_camera; + for (auto& node : p_nodes) + node.data()->destroy(); + } + + inline + void camera(Camera* camera) + { + p_camera = camera; + } + + inline + Camera* camera() + { + return p_camera; + } + + inline + void add(const hpr::TreeNode& entityNode) + { + p_nodes.push(entityNode); + } + + inline + darray>& nodes() + { + return p_nodes; + } + + void render() + { + for (auto node : p_nodes) + { + node.data()->render(); + + node.data()->shaderProgram()->bind(); + // camera + node.data()->shaderProgram()->uniformMatrix<4, 4>("view", 1, true, p_camera->view().data()); + node.data()->shaderProgram()->uniformMatrix<4, 4>("projection", 1, true, p_camera->projection().data()); + node.data()->shaderProgram()->uniformVector("viewPos", 1, p_camera->position().data()); + + // light + hpr::vec3 lightColor {1.0f, 1.0f, 1.0f}; + node.data()->shaderProgram()->uniformVector("lightColor", 1, lightColor.data()); + hpr::vec3 lightPos {1.0f, 1.0f, 1.0f}; + node.data()->shaderProgram()->uniformVector("lightPos", 1, lightPos.data()); + + node.data()->shaderProgram()->unbind(); + + //for (auto descendant : node.descendants()) + // descendant->data()->render(); + } + } + +}; diff --git a/source/creator/shaders.hpp b/source/creator/shaders.hpp new file mode 100644 index 0000000..3cb27e0 --- /dev/null +++ b/source/creator/shaders.hpp @@ -0,0 +1,145 @@ +#pragma once + +const char* vertexSource = R"glsl( + +#version 430 core +layout (location = 0) in vec3 position; +layout (location = 1) in vec3 normal; +layout (location = 2) in vec3 color; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +uniform vec3 lightPos; + +out vec3 Normal; +out vec3 Color; +out vec3 FragPos; +out vec3 LightPos; + +void main() +{ + gl_Position = projection * view * model * vec4(position, 1.0); + gl_PointSize = 5; + Normal = mat3(transpose(inverse(model))) * normal; + Color = color; + FragPos = vec3(view * model * vec4(position, 1.0)); + LightPos = lightPos; vec3(view * vec4(lightPos, 1.0)); +} + +)glsl"; + + +const char* fragmentSource = R"glsl( + +#version 430 core + +in vec3 Normal; +in vec3 Color; +in vec3 FragPos; +in vec3 LightPos; + +//uniform vec3 lightPos; +uniform vec3 lightColor; +uniform vec4 objectColor; +uniform vec3 viewPos; + +out vec4 FragColor; + +void main() +{ + float ambientStrength = 0.1; + vec3 ambient = ambientStrength * lightColor; + + // diffuse + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(LightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * lightColor; + + // specular + float specularStrength = 0.2; + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); + vec3 specular = specularStrength * spec * lightColor; + + vec3 result = (ambient + diffuse) * vec3(objectColor); + FragColor = vec4(result, 1.0); +} + +)glsl"; + +const char* gridVertexSource = R"glsl( + +#version 430 core +uniform mat4 view; +uniform mat4 projection; +vec3 position = vec3(0, 0, 0); + +// Grid position are in xy clipped space +vec3 gridPlane[6] = vec3[]( + vec3(1, 1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), + vec3(-1, -1, 0), vec3(1, 1, 0), vec3(1, -1, 0) +); +// normal vertice projection +void main() { + gl_Position = projection * view * vec4(gridPlane[gl_VertexID].xyz, 1.0); +} + + +)glsl"; + +const char* gridFragmentSource = R"glsl( + +#version 430 core + +const float tileSize = 0.2; +const float borderSize = 0.468; +const float lineBlur = 0.2; + +vec2 aGrid(vec2 uv) { + return vec2(mod(uv.x, 1.0), mod(uv.y * 2.0, 1.0)); +} + +vec2 bGrid(vec2 uv) { + return vec2(mod(uv.x - 0.5, 1.0), mod((uv.y * 2.0) - 0.5, 1.0)); +} + +float los(vec2 pos) { + vec2 abspos = abs(pos - vec2(0.5)); + return smoothstep(borderSize, borderSize + lineBlur, abspos.x + abspos.y); +} +out vec4 fragColor; +void main() { + vec2 uv = vec2(1.0, 0.);//fragCoord;// / iResolution.xy; + vec2 size = uv / tileSize; + float alos = los(aGrid(size)); + float blos = los(bGrid(size)); + float color = min(alos, blos); + color = pow(color, 1.0 / 2.2); + fragColor = vec4(color); +} + +)glsl"; + +/* +float4 HeatMapColor(float value, float minValue, float maxValue) +{ + #define HEATMAP_COLORS_COUNT 6 + float4 colors[HEATMAP_COLORS_COUNT] = + { + float4(0.32, 0.00, 0.32, 1.00), + float4(0.00, 0.00, 1.00, 1.00), + float4(0.00, 1.00, 0.00, 1.00), + float4(1.00, 1.00, 0.00, 1.00), + float4(1.00, 0.60, 0.00, 1.00), + float4(1.00, 0.00, 0.00, 1.00), + }; + float ratio=(HEATMAP_COLORS_COUNT-1.0)*saturate((value-minValue)/(maxValue-minValue)); + float indexMin=floor(ratio); + float indexMax=min(indexMin+1,HEATMAP_COLORS_COUNT-1); + return lerp(colors[indexMin], colors[indexMax], ratio-indexMin); +} + * */ \ No newline at end of file diff --git a/source/creator/test.cpp b/source/creator/test.cpp index 719463b..5d934f5 100644 --- a/source/creator/test.cpp +++ b/source/creator/test.cpp @@ -1,269 +1,269 @@ -#ifndef __gl_h_ -#include -#endif -#include "hpr/window_system/window_system.hpp" -#include "hpr/window_system/glfw/window_system.hpp" -#include "hpr/window_system/glfw/window.hpp" -#include "hpr/gpu.hpp" -#include "hpr/math.hpp" -#include "hpr/mesh.hpp" - -#include -#include -#include -#include - -const char *vertexShaderSource = "#version 330 core\n" - "layout (location = 0) in vec3 aPos;\n" - "void main()\n" - "{\n" - " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" - "}\0"; -const char *fragmentShaderSource = "#version 330 core\n" - "out vec4 FragColor;\n" - "void main()\n" - "{\n" - " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" - "}\n\0"; -const GLchar* vertexSource = R"glsl( - #version 150 core - in vec3 position; - in vec3 color; - in vec2 texcoord; - out vec3 Color; - out vec2 Texcoord; - uniform mat4 model; - uniform mat4 view; - uniform mat4 proj; - uniform vec3 overrideColor; - void main() - { - Color = overrideColor * color; - Texcoord = texcoord; - gl_Position = proj * view * model * vec4(position, 1.0); - } -)glsl"; -int main() -{ - using namespace hpr; - - gpu::WindowSystem *ws = gpu::WindowSystem::create(gpu::WindowContext::Provider::GLFW); - gpu::Window *w = ws->newWindow(); - w->init("test", gpu::Window::Style::Windowed, 0, 0, 600, 400, nullptr, nullptr); - - if (gpu::opengl::Device::loadLoader()) - std::cerr << "Load gl loader error" << std::endl; - - // build and compile our shader program - // ------------------------------------ - // vertex shader - /*unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); - glCompileShader(vertexShader); - // check for shader compile errors - int success; - char infoLog[512]; - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); - if (!success) - { - glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; - } - // fragment shader - unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); - glCompileShader(fragmentShader); - // check for shader compile errors - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); - if (!success) - { - glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; - } - // link shaders - unsigned int shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glLinkProgram(shaderProgram); - // check for linking errors - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); - if (!success) { - glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; - } - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); -*/ - mesh::Mesh mesh; - mesh.addVertex(-1, 1, 0.5); - mesh.addVertex(1, 1, 0.5); - mesh.addVertex(1, -1, 0.5); - mesh.addVertex(-1, -1, 0.5); - mesh.addEdge(mesh.vertex(0), mesh.vertex(1)); - mesh.addEdge(mesh.vertex(1), mesh.vertex(2)); - mesh.addEdge(mesh.vertex(2), mesh.vertex(3)); - mesh.addEdge(mesh.vertex(3), mesh.vertex(0)); - mesh.addEdge(mesh.vertex(0), mesh.vertex(2)); - mesh.addFace(mesh.edge(0), mesh.edge(1), mesh.edge(4)); - mesh.addFace(mesh.edge(2), mesh.edge(3), mesh.edge(4)); - - /*darray data (3 * 6, 0.f); - auto arr = mesh.face(0)->vertices() + mesh.face(1)->vertices(); - for (auto n = 0; n < arr.size(); ++n) - for (auto k = 0; k < 3; ++k) - data[k + 3 * n] = *(arr[n]->data() + k);*/ - darray indices (6, 0); - darray data; - for (auto v : mesh.vertices()) - for (auto c : *v) - data.push(c); - - indices[0] = 3; - indices[1] = 1; - indices[2] = 0; - indices[3] = 1; - indices[4] = 2; - indices[5] = 3; - - std::cout << "Data: "; - for (auto p : data) - std::cout << p << " "; - std::cout << std::endl; - std::cout << "Indices: "; - for (auto p : indices) - std::cout << p << " "; - std::cout << std::endl; - - darray vertices { - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f // top left - }; - darray indices2 { // note that we start from 0! - 0, 1, 3, // first Triangle - 1, 2, 3 // second Triangle - }; - - /* - unsigned int VBO, VAO, EBO; - glGenVertexArrays(1, &VAO); - glGenBuffers(1, &VBO); - glGenBuffers(1, &EBO); - // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). - glBindVertexArray(VAO); - - glBindBuffer(GL_ARRAY_BUFFER, VBO); - //glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//data.size(), data.data(), GL_STATIC_DRAW); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - //glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);//indices.size(), indices.data(), GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices2.size(), indices2.data(), GL_STATIC_DRAW); - - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind - glBindBuffer(GL_ARRAY_BUFFER, 0); - - // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. - //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other - // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. - glBindVertexArray(0); -*/ - gpu::Device* device; - gpu::Device::create(&device, gpu::Device::DeviceAPI::OpenGL); - device->initialize(); - gpu::Shader* vertexShader; - device->createVertexShader(&vertexShader, "shaders/test.vert.glsl", "VS"); - gpu::Shader* fragmentShader; - device->createFragmentShader(&fragmentShader, "shaders/test.frag.glsl", "FS"); - gpu::ShaderProgram* shaderProgram; - device->createShaderProgram(&shaderProgram); - device->attachShader(shaderProgram, vertexShader); - device->attachShader(shaderProgram, fragmentShader); - device->linkProgram(shaderProgram); - - gpu::Buffer* vertexBuffer; - device->createVertexBuffer(&vertexBuffer, sizeof(float) * data.size(), (char*)data.data()); - gpu::Buffer* indexBuffer; - device->createIndexBuffer(&indexBuffer, sizeof(unsigned short) * indices.size(), (char*)indices.data()); - - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ImGui::StyleColorsDark(); - - ImGui_ImplGlfw_InitForOpenGL(dynamic_cast(w)->instance(), true); - ImGui_ImplOpenGL3_Init("#version 420"); - - while (w->isOpen()) - { - glViewport(0, 0, 600, 400); - - glEnable(GL_DEPTH_TEST); - glClearColor(0.2f, 0.2f, 0.2f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // draw our first triangle - /*glUseProgram(shaderProgram); - glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized - //glDrawArrays(GL_TRIANGLES, 0, 6); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);*/ - device->useShaderProgram(shaderProgram); - device->useVertexBuffer(vertexBuffer, 0, 0); - device->useIndexBuffer(indexBuffer, 0); - //dynamic_cast(device)->Draw(2, 0, 0); - glDrawArrays(GL_TRIANGLES, 0, 6); - //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - bool yes = true; - ImGui::ShowDemoWindow(&yes); - - ImGui::Begin("Hello, world!"); - { - if (ImGui::Button("Exit")) - w->state(gpu::Window::State::Closed); - ImGui::End(); - } - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - dynamic_cast(w)->swapBuffers(); - dynamic_cast(w)->pollEvents(); - } - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - device->destroyShaderProgram(shaderProgram, false); - device->destroyShader(vertexShader); - device->destroyShader(fragmentShader); - device->destroyBuffer(vertexBuffer); - device->destroyBuffer(indexBuffer); - delete dynamic_cast(device); - - /*glDeleteVertexArrays(1, &VAO); - glDeleteBuffers(1, &VBO); - glDeleteBuffers(1, &EBO); - glDeleteProgram(shaderProgram);*/ - - ws->destroyWindow(w); - gpu::WindowSystem::destroy(ws); - - return 0; +#ifndef __gl_h_ +#include +#endif +#include "hpr/window_system/window_system.hpp" +#include "hpr/window_system/glfw/window_system.hpp" +#include "hpr/window_system/glfw/window.hpp" +#include "hpr/gpu.hpp" +#include "hpr/math.hpp" +#include "hpr/mesh.hpp" + +#include +#include +#include +#include + +const char *vertexShaderSource = "#version 330 core\n" + "layout (location = 0) in vec3 aPos;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" + "}\0"; +const char *fragmentShaderSource = "#version 330 core\n" + "out vec4 FragColor;\n" + "void main()\n" + "{\n" + " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" + "}\n\0"; +const GLchar* vertexSource = R"glsl( + #version 150 core + in vec3 position; + in vec3 color; + in vec2 texcoord; + out vec3 Color; + out vec2 Texcoord; + uniform mat4 model; + uniform mat4 view; + uniform mat4 proj; + uniform vec3 overrideColor; + void main() + { + Color = overrideColor * color; + Texcoord = texcoord; + gl_Position = proj * view * model * vec4(position, 1.0); + } +)glsl"; +int main() +{ + using namespace hpr; + + gpu::WindowSystem *ws = gpu::WindowSystem::create(gpu::WindowContext::Provider::GLFW); + gpu::Window *w = ws->newWindow(); + w->init("test", gpu::Window::Style::Windowed, 0, 0, 600, 400, nullptr, nullptr); + + if (gpu::opengl::Device::loadLoader()) + std::cerr << "Load gl loader error" << std::endl; + + // build and compile our shader program + // ------------------------------------ + // vertex shader + /*unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); + glCompileShader(vertexShader); + // check for shader compile errors + int success; + char infoLog[512]; + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + if (!success) + { + glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); + std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; + } + // fragment shader + unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); + glCompileShader(fragmentShader); + // check for shader compile errors + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); + if (!success) + { + glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); + std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; + } + // link shaders + unsigned int shaderProgram = glCreateProgram(); + glAttachShader(shaderProgram, vertexShader); + glAttachShader(shaderProgram, fragmentShader); + glLinkProgram(shaderProgram); + // check for linking errors + glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); + std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; + } + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); +*/ + mesh::Mesh mesh; + mesh.addVertex(-1, 1, 0.5); + mesh.addVertex(1, 1, 0.5); + mesh.addVertex(1, -1, 0.5); + mesh.addVertex(-1, -1, 0.5); + mesh.addEdge(mesh.vertex(0), mesh.vertex(1)); + mesh.addEdge(mesh.vertex(1), mesh.vertex(2)); + mesh.addEdge(mesh.vertex(2), mesh.vertex(3)); + mesh.addEdge(mesh.vertex(3), mesh.vertex(0)); + mesh.addEdge(mesh.vertex(0), mesh.vertex(2)); + mesh.addFace(mesh.edge(0), mesh.edge(1), mesh.edge(4)); + mesh.addFace(mesh.edge(2), mesh.edge(3), mesh.edge(4)); + + /*darray data (3 * 6, 0.f); + auto arr = mesh.face(0)->vertices() + mesh.face(1)->vertices(); + for (auto n = 0; n < arr.size(); ++n) + for (auto k = 0; k < 3; ++k) + data[k + 3 * n] = *(arr[n]->data() + k);*/ + darray indices (6, 0); + darray data; + for (auto v : mesh.vertices()) + for (auto c : *v) + data.push(c); + + indices[0] = 3; + indices[1] = 1; + indices[2] = 0; + indices[3] = 1; + indices[4] = 2; + indices[5] = 3; + + std::cout << "Data: "; + for (auto p : data) + std::cout << p << " "; + std::cout << std::endl; + std::cout << "Indices: "; + for (auto p : indices) + std::cout << p << " "; + std::cout << std::endl; + + darray vertices { + 0.5f, 0.5f, 0.0f, // top right + 0.5f, -0.5f, 0.0f, // bottom right + -0.5f, -0.5f, 0.0f, // bottom left + -0.5f, 0.5f, 0.0f // top left + }; + darray indices2 { // note that we start from 0! + 0, 1, 3, // first Triangle + 1, 2, 3 // second Triangle + }; + + /* + unsigned int VBO, VAO, EBO; + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). + glBindVertexArray(VAO); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + //glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//data.size(), data.data(), GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + //glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);//indices.size(), indices.data(), GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices2.size(), indices2.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. + //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other + // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. + glBindVertexArray(0); +*/ + gpu::Device* device; + gpu::Device::create(&device, gpu::Device::DeviceAPI::OpenGL); + device->initialize(); + gpu::Shader* vertexShader; + device->createVertexShader(&vertexShader, "shaders/test.vert.glsl", "VS"); + gpu::Shader* fragmentShader; + device->createFragmentShader(&fragmentShader, "shaders/test.frag.glsl", "FS"); + gpu::ShaderProgram* shaderProgram; + device->createShaderProgram(&shaderProgram); + device->attachShader(shaderProgram, vertexShader); + device->attachShader(shaderProgram, fragmentShader); + device->linkProgram(shaderProgram); + + gpu::Buffer* vertexBuffer; + device->createVertexBuffer(&vertexBuffer, sizeof(float) * data.size(), (char*)data.data()); + gpu::Buffer* indexBuffer; + device->createIndexBuffer(&indexBuffer, sizeof(unsigned short) * indices.size(), (char*)indices.data()); + + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ImGui::StyleColorsDark(); + + ImGui_ImplGlfw_InitForOpenGL(dynamic_cast(w)->instance(), true); + ImGui_ImplOpenGL3_Init("#version 420"); + + while (w->isOpen()) + { + glViewport(0, 0, 600, 400); + + glEnable(GL_DEPTH_TEST); + glClearColor(0.2f, 0.2f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw our first triangle + /*glUseProgram(shaderProgram); + glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized + //glDrawArrays(GL_TRIANGLES, 0, 6); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);*/ + device->useShaderProgram(shaderProgram); + device->useVertexBuffer(vertexBuffer, 0, 0); + device->useIndexBuffer(indexBuffer, 0); + //dynamic_cast(device)->Draw(2, 0, 0); + glDrawArrays(GL_TRIANGLES, 0, 6); + //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + bool yes = true; + ImGui::ShowDemoWindow(&yes); + + ImGui::Begin("Hello, world!"); + { + if (ImGui::Button("Exit")) + w->state(gpu::Window::State::Closed); + ImGui::End(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + dynamic_cast(w)->swapBuffers(); + dynamic_cast(w)->pollEvents(); + } + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + device->destroyShaderProgram(shaderProgram, false); + device->destroyShader(vertexShader); + device->destroyShader(fragmentShader); + device->destroyBuffer(vertexBuffer); + device->destroyBuffer(indexBuffer); + delete dynamic_cast(device); + + /*glDeleteVertexArrays(1, &VAO); + glDeleteBuffers(1, &VBO); + glDeleteBuffers(1, &EBO); + glDeleteProgram(shaderProgram);*/ + + ws->destroyWindow(w); + gpu::WindowSystem::destroy(ws); + + return 0; } \ No newline at end of file diff --git a/source/creator/test2-back.cpp b/source/creator/test2-back.cpp new file mode 100644 index 0000000..e6d4df5 --- /dev/null +++ b/source/creator/test2-back.cpp @@ -0,0 +1,551 @@ + + +/* +#include "hpr/gpu.hpp" +#include "hpr/window_system/window_system.hpp" +#include "hpr/window_system/glfw/window_system.hpp" +#include "hpr/window_system/glfw/window.hpp" +#include "hpr/math.hpp" +#include "hpr/mesh.hpp" + +#include +#include +#include +void GLAPIENTRY +MessageCallback( GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam ) +{ + fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n\n", + ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), + type, severity, message ); +} +*/ + +/* +const char *vertexShaderSource = "#version 430 core\n" + "layout (location = 0) in vec3 aPos;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" + "}\0"; +const char *fragmentShaderSource = "#version 430 core\n" + "out vec4 FragColor;\n" + "void main()\n" + "{\n" + " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" + "}\n\0"; +const GLchar* vertexSource = R"glsl( + #version 150 core + in vec2 position; + in vec3 color; + out vec3 Color; + void main() + { + Color = color; + gl_Position = vec4(position, 0.0, 1.0); + } +)glsl"; +const GLchar* fragmentSource = R"glsl( + #version 150 core + in vec3 Color; + out vec4 outColor; + void main() + { + outColor = vec4(Color, 1.0); + } +)glsl"; +int main() +{ + using namespace hpr; + + gpu::WindowSystem *ws = gpu::WindowSystem::create(gpu::WindowContext::Provider::GLFW); + gpu::Window *w = ws->newWindow(); + w->init("test", gpu::Window::Style::Windowed, 50, 50, 600, 400, nullptr, nullptr); + + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) + throw std::runtime_error("Cannot initialize gl context"); + +// During init, enable debug output + glEnable ( GL_DEBUG_OUTPUT ); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback( glDebugOutput, nullptr); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + gpu::Shader vshader {gpu::Shader::Type::Vertex, vertexShaderSource}; + gpu::Shader fshader {gpu::Shader::Type::Fragment, fragmentShaderSource}; + gpu::ShaderProgram sprogram {}; + + vshader.create(); + fshader.create(); + sprogram.create(); + sprogram.attach(vshader); + sprogram.attach(fshader); + sprogram.link(); + +gpu::Shader vshader2 {gpu::Shader::Type::Vertex, vertexSource}; + gpu::Shader fshader2 {gpu::Shader::Type::Fragment, fragmentSource}; + gpu::ShaderProgram sprogram2 {}; + + vshader2.create(); + fshader2.create(); + sprogram2.create(); + sprogram2.attach(vshader2); + sprogram2.attach(fshader2); + sprogram2.link(); + + //vshader.destroy(); + //fshader.destroy(); + for (auto& sh : sprogram.shaders()) + std::cout << sh.index() << std::endl; + std::cout << sprogram.index() << std::endl; + + darray vertices { + 0.5f, 0.5f, 0.0f, // top right + 0.5f, -0.5f, 0.0f, // bottom right + -0.5f, -0.5f, 0.0f, // bottom left + -0.5f, 0.5f, 0.0f // top left + }; + darray indices2 { // note that we start from 0! + 0, 1, 3, // first Triangle + 1, 2, 3 // second Triangle + }; + + gpu::ArrayObject vao {}; + gpu::BufferObject vbo {gpu::BufferObject::Type::Vertex}; + gpu::BufferObject ebo {gpu::BufferObject::Type::Index}; + + vao.create(); + vao.bind(); + vbo.create(vertices); + ebo.create(indices2); + vao.attribPointer(vbo, 0, 3); + vao.unbind(); + + // Create Vertex Array Object + GLuint vao2; + glGenVertexArrays(1, &vao2); + glBindVertexArray(vao2); + + // Create a Vertex Buffer Object and copy the vertex data to it + GLuint vbo2; + glGenBuffers(1, &vbo2); + + GLfloat vertices2[] = { + -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Top-left + 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // Top-right + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right + -0.5f, -0.5f, 1.0f, 1.0f, 1.0f // Bottom-left + }; + + glBindBuffer(GL_ARRAY_BUFFER, vbo2); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices2, GL_STATIC_DRAW); + + // Create an element array + GLuint ebo2; + glGenBuffers(1, &ebo2); + + GLuint elements2[] = { + 0, 1, 2, + 2, 3, 0 + }; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo2); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements2), elements2, GL_STATIC_DRAW); + // Specify the layout of the vertex data + GLint posAttrib = glGetAttribLocation(sprogram2.index(), "position"); + glEnableVertexAttribArray(posAttrib); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0); + + GLint colAttrib = glGetAttribLocation(sprogram2.index(), "color"); + glEnableVertexAttribArray(colAttrib); + glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); + std::cout << sprogram2.index() << std::endl; + gpu::Viewport viewport {{0, 0}, {600, 400}}; + gpu::ColorBuffer colorBuffer; + gpu::DepthBuffer depthBuffer {true}; + + /*IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.IniFilename = nullptr; + io.LogFilename = nullptr; + + ImGui::StyleColorsDark(); + + ImGui_ImplGlfw_InitForOpenGL(dynamic_cast(w)->instance(), true); + ImGui_ImplOpenGL3_Init("#version 430"); + + while (w->isOpen()) + { + viewport.set(); + + //colorBuffer.clear({0.2f, 0.2f, 0.2f, 1.0f}); + //depthBuffer.clear(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + sprogram.bind(); + vao.bind(); + ebo.bind(); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); + + sprogram2.bind(); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +/* + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + bool yes = true; + ImGui::ShowDemoWindow(&yes); + + ImGui::Begin("Hello, world!"); + { + if (ImGui::Button("Exit")) + w->state(gpu::Window::State::Closed); + ImGui::End(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + dynamic_cast(w)->swapBuffers(); + dynamic_cast(w)->pollEvents(); + } + + sprogram.destroy(); + + vbo.destroy(); + ebo.destroy(); + vao.destroy(); +/* + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + ws->destroyWindow(w); + gpu::WindowSystem::destroy(ws); + + return 0; +} +*/ + + +// Link statically with GLEW +#define GLEW_STATIC + +// Headers +//#include +//#include + +#include "hpr/gpu.hpp" + +#include + +void APIENTRY glDebugOutput( + GLenum source, + GLenum type, + unsigned int id, + GLenum severity, + GLsizei length, + const char* message, + const void* userParam +) +{ + // ignore non-significant error/warning codes + if (id == 131169 || id == 131185 || id == 131218 || id == 131204) + return; + + std::cout << "Debug::GL[" << id << "]: " << message << std::endl; + + switch (source) + { + case GL_DEBUG_SOURCE_API: + std::cout << "Source: API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + std::cout << "Source: Window System"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + std::cout << "Source: Shader Compiler"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + std::cout << "Source: Third Party"; + break; + case GL_DEBUG_SOURCE_APPLICATION: + std::cout << "Source: Application"; + break; + case GL_DEBUG_SOURCE_OTHER: + std::cout << "Source: Other"; + break; + } + std::cout << std::endl; + + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + std::cout << "Type: Error"; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + std::cout << "Type: Deprecated Behaviour"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + std::cout << "Type: Undefined Behaviour"; + break; + case GL_DEBUG_TYPE_PORTABILITY: + std::cout << "Type: Portability"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + std::cout << "Type: Performance"; + break; + case GL_DEBUG_TYPE_MARKER: + std::cout << "Type: Marker"; + break; + case GL_DEBUG_TYPE_PUSH_GROUP: + std::cout << "Type: Push Group"; + break; + case GL_DEBUG_TYPE_POP_GROUP: + std::cout << "Type: Pop Group"; + break; + case GL_DEBUG_TYPE_OTHER: + std::cout << "Type: Other"; + break; + } + std::cout << std::endl; + + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: + std::cout << "Severity: high"; + break; + case GL_DEBUG_SEVERITY_MEDIUM: + std::cout << "Severity: medium"; + break; + case GL_DEBUG_SEVERITY_LOW: + std::cout << "Severity: low"; + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: + std::cout << "Severity: notification"; + break; + } + std::cout << std::endl; +} + +// Shader sources +const GLchar* vertexSource = R"glsl( + #version 430 core +layout (location = 0) in vec2 position; +layout (location = 1) in vec3 color; + + //in vec2 position; + //in vec3 color; + out vec3 Color; + void main() + { + Color = color; + gl_Position = vec4(position, 0.0, 1.0); + } +)glsl"; +const GLchar* fragmentSource = R"glsl( + #version 430 core + in vec3 Color; + out vec4 outColor; + void main() + { + //outColor = vec4(1.0, 1.0, 1.0, 1.0); //vec4(Color, 1.0); + outColor = vec4(Color, 1.0); + } +)glsl"; + +int main() +{ + /*sf::ContextSettings settings; + settings.depthBits = 24; + settings.stencilBits = 8; + settings.majorVersion = 3; + settings.minorVersion = 2; + + sf::Window window(sf::VideoMode(800, 600, 32), "OpenGL", sf::Style::Titlebar | sf::Style::Close, settings); +*/ + + using namespace hpr; +//gpu::WindowSystem *ws = gpu::WindowSystem::create(gpu::WindowContext::Provider::GLFW); + //gpu::Window *w = ws->newWindow(); + //w->init("test", gpu::Window::Style::Windowed, 50, 50, 600, 400, nullptr, nullptr); + gpu::Window w {50, 50, 600, 400, "Test", gpu::Window::Style::Windowed, nullptr, nullptr}; + + w.keyClickCallback([](gpu::Window* window, int key, int scancode, int action, int mods) + { + if (key == GLFW_KEY_ESCAPE) + window->state(gpu::Window::Closed); + }); + + w.context().debug(); + // Initialize GLEW + //glewExperimental = GL_TRUE; + //glewInit(); + + // Create Vertex Array Object + /*GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao);*/ + + gpu::ArrayObject vao {}; + vao.create(); + vao.bind(); + + // Create a Vertex Buffer Object and copy the vertex data to it + //GLuint vbo; + //glGenBuffers(1, &vbo); + gpu::BufferObject vbo2 {gpu::BufferObject::Type::Vertex}; + + darray vertices { + -0.5f, 0.5f, //1.0f, 0.0f, 0.0f, // Top-left + 0.5f, 0.5f, //0.0f, 1.0f, 0.0f, // Top-right + 0.5f, -0.5f, //0.0f, 0.0f, 1.0f, // Bottom-right + -0.5f, -0.5f, //1.0f, 1.0f, 1.0f // Bottom-left + }; + darray colors { + 1.0f, 0.0f, 0.0f, // Top-left + 0.0f, 1.0f, 0.0f, // Top-right + 0.0f, 0.0f, 1.0f, // Bottom-right + 1.0f, 1.0f, 1.0f // Bottom-left + }; + float vertices2[] = { + -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Top-left + 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // Top-right + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right + -0.5f, -0.5f, 1.0f, 1.0f, 1.0f // Bottom-left + }; + + //glBindBuffer(GL_ARRAY_BUFFER, vbo); + //glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW); + //glBindBuffer(GL_ARRAY_BUFFER, vbo.index()); + //glBufferData(static_cast(gpu::BufferObject::Type::Vertex), sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW); + //vbo.bind(); + vbo2.create(vertices); + vbo2.bind(); + + gpu::BufferObject vboColors {gpu::BufferObject::Type::Vertex}; + vboColors.create(colors); + vboColors.bind(); + //vbo2.destroy(); + + // Create an element array + /*GLuint ebo; + glGenBuffers(1, &ebo); + + GLuint elements[] = { + 0, 1, 2, + 2, 3, 0 + };*/ + darray elements { + 0, 1, 2, + 2, 3, 0 + }; +/* + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW); +*/ + gpu::BufferObject els {gpu::BufferObject::Type::Index}; + els.create(elements); + els.bind(); + // Create and compile the vertex shader + /*GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, &vertexSource, NULL); + glCompileShader(vertexShader);*/ + + gpu::Shader vshader {gpu::Shader::Type::Vertex, vertexSource}; + vshader.create(); + + // Create and compile the fragment shader + /*GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &fragmentSource, NULL); + glCompileShader(fragmentShader);*/ + + gpu::Shader fshader {gpu::Shader::Type::Fragment, fragmentSource}; + fshader.create(); + + // Link the vertex and fragment shader into a shader program + /*GLuint shaderProgram = glCreateProgram(); + glAttachShader(shaderProgram, vertexShader); + glAttachShader(shaderProgram, fragmentShader); + //glBindFragDataLocation(shaderProgram, 0, "outColor"); + glLinkProgram(shaderProgram); + glUseProgram(shaderProgram);*/ + + gpu::ShaderProgram sprogram {}; + sprogram.create(); + sprogram.attach(vshader); + sprogram.attach(fshader); + sprogram.link(); + sprogram.bind(); + + // Specify the layout of the vertex data + /*GLint posAttrib = glGetAttribLocation(sprogram.index(), "position"); + glEnableVertexAttribArray(posAttrib); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);*/ + vao.attribPointer(vbo2, 0, 2); + vao.attribPointer(vboColors, 1, 3); + /*vbo2.bind(); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0); + vboColors.bind(); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);*/ + /*GLint colAttrib = glGetAttribLocation(sprogram.index(), "color"); + glEnableVertexAttribArray(colAttrib); + glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));*/ + + gpu::ColorBuffer cb; + + bool running = true; + while (w.state() != gpu::Window::Closed) + { + /*sf::Event windowEvent; + while (window.pollEvent(windowEvent)) + { + switch (windowEvent.type) + { + case sf::Event::Closed: + running = false; + break; + } + }*/ + + // Clear the screen to black + //glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + //glClear(GL_COLOR_BUFFER_BIT); + cb.clear(vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + // Draw a rectangle from the 2 triangles using 6 indices + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); + + // Swap buffers + //window.display(); + w.swapBuffers(); + w.pollEvents(); + } + + sprogram.destroy(); + vshader.destroy(); + fshader.destroy(); + /*glDeleteProgram(shaderProgram); + glDeleteShader(fragmentShader); + glDeleteShader(vertexShader);*/ + + //glDeleteBuffers(1, &ebo); + //glDeleteBuffers(1, &vbo); + vbo2.destroy(); + + //glDeleteVertexArrays(1, &vao); + vao.destroy(); + w.destroy(); + + return 0; +} diff --git a/source/creator/test2.cpp b/source/creator/test2.cpp index f388132..66f1215 100644 --- a/source/creator/test2.cpp +++ b/source/creator/test2.cpp @@ -1,133 +1,319 @@ - -#include "hpr/gpu.hpp" -#include "hpr/window_system/window_system.hpp" -#include "hpr/window_system/glfw/window_system.hpp" -#include "hpr/window_system/glfw/window.hpp" -#include "hpr/math.hpp" -#include "hpr/mesh.hpp" - -#include -#include -#include -#include - -const char *vertexShaderSource = "#version 330 core\n" - "layout (location = 0) in vec3 aPos;\n" - "void main()\n" - "{\n" - " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" - "}\0"; -const char *fragmentShaderSource = "#version 330 core\n" - "out vec4 FragColor;\n" - "void main()\n" - "{\n" - " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" - "}\n\0"; - -int main() -{ - using namespace hpr; - - gpu::WindowSystem *ws = gpu::WindowSystem::create(gpu::WindowContext::Provider::GLFW); - gpu::Window *w = ws->newWindow(); - w->init("test", gpu::Window::Style::Windowed, 0, 0, 600, 400, nullptr, nullptr); - - if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) - throw std::runtime_error("Cannot initialize gl context"); - - gpu::Shader vshader {gpu::Shader::Type::Vertex, vertexShaderSource}; - gpu::Shader fshader {gpu::Shader::Type::Fragment, fragmentShaderSource}; - gpu::ShaderProgram sprogram {}; - - vshader.create(); - fshader.create(); - sprogram.create(); - sprogram.attach(vshader); - sprogram.attach(fshader); - sprogram.link(); - for (auto& sh : sprogram.shaders()) - std::cout << sh.index() << std::endl; - - - darray vertices { - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f // top left - }; - darray indices2 { // note that we start from 0! - 0, 1, 3, // first Triangle - 1, 2, 3 // second Triangle - }; - - gpu::ArrayObject vao {}; - gpu::BufferObject vbo {gpu::BufferObject::Type::Vertex}; - gpu::BufferObject ebo {gpu::BufferObject::Type::Index}; - - vao.create(); - vao.bind(); - vbo.create(vertices); - ebo.create(indices2); - vao.attribPointer(vbo, 0, 3); - vao.unbind(); - - gpu::Viewport viewport {{0, 0}, {600, 400}}; - gpu::ColorBuffer colorBuffer; - gpu::DepthBuffer depthBuffer {true}; - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ImGui::StyleColorsDark(); - - ImGui_ImplGlfw_InitForOpenGL(dynamic_cast(w)->instance(), true); - ImGui_ImplOpenGL3_Init("#version 420"); - - while (w->isOpen()) - { - viewport.set(); - - colorBuffer.clear({0.8f, 0.2f, 0.2f, 1.0f}); - depthBuffer.clear(); - - sprogram.bind(); - vao.bind(); - - glDrawArrays(GL_TRIANGLES, 0, 6); - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - bool yes = true; - ImGui::ShowDemoWindow(&yes); - - ImGui::Begin("Hello, world!"); - { - if (ImGui::Button("Exit")) - w->state(gpu::Window::State::Closed); - ImGui::End(); - } - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - dynamic_cast(w)->swapBuffers(); - dynamic_cast(w)->pollEvents(); - } - - sprogram.destroy(); - vshader.destroy(); - fshader.destroy(); - vbo.destroy(); - ebo.destroy(); - vao.destroy(); - - ws->destroyWindow(w); - gpu::WindowSystem::destroy(ws); - - return 0; -} \ No newline at end of file + +#include + +#include "visual.hpp" +#include "entity.hpp" +#include "ui.hpp" +//#include "transform.hpp" +#include "camera.hpp" +#include "scene.hpp" +#include "grid.hpp" +#include +#include "ray.hpp" + + +int main() +{ + + Visual visual; + + visual.window()->keyClickCallback([](gpu::Window* window, int key, int scancode, int action, int mods) + { + if (key == GLFW_KEY_ESCAPE) + window->state(gpu::Window::Closed); + }); + + visual.window()->context().debug(); + + UI ui {visual.window()}; + + + darray vertices { + /*vec2(-0.5f, 0.5f), + vec2(0.5f, 0.5f), + vec2(0.5f, -0.5f), + vec2(-0.5f, -0.5f)*/ + vec3(-0.5f, -0.5f, -0.5f), + vec3(0.5f, -0.5f, -0.5f), + vec3(0.5f, 0.5f, -0.5f), + vec3(0.5f, 0.5f, -0.5f), + vec3(-0.5f, 0.5f, -0.5f), + vec3(-0.5f, -0.5f, -0.5f), + + vec3(-0.5f, -0.5f, 0.5f), + vec3(0.5f, -0.5f, 0.5f), + vec3(0.5f, 0.5f, 0.5f), + vec3(0.5f, 0.5f, 0.5f), + vec3(-0.5f, 0.5f, 0.5f), + vec3(-0.5f, -0.5f, 0.5f), + + vec3(-0.5f, 0.5f, 0.5f), + vec3(-0.5f, 0.5f, -0.5f), + vec3(-0.5f, -0.5f, -0.5f), + vec3(-0.5f, -0.5f, -0.5f), + vec3(-0.5f, -0.5f, 0.5f), + vec3(-0.5f, 0.5f, 0.5f), + + vec3(0.5f, 0.5f, 0.5f), + vec3(0.5f, 0.5f, -0.5f), + vec3(0.5f, -0.5f, -0.5f), + vec3(0.5f, -0.5f, -0.5f), + vec3(0.5f, -0.5f, 0.5f), + vec3(0.5f, 0.5f, 0.5f), + + vec3(-0.5f, -0.5f, -0.5f), + vec3(0.5f, -0.5f, -0.5f), + vec3(0.5f, -0.5f, 0.5f), + vec3(0.5f, -0.5f, 0.5f), + vec3(-0.5f, -0.5f, 0.5f), + vec3(-0.5f, -0.5f, -0.5f), + + vec3(-0.5f, 0.5f, -0.5f), + vec3(0.5f, 0.5f, -0.5f), + vec3(0.5f, 0.5f, 0.5f), + vec3(0.5f, 0.5f, 0.5f), + vec3(-0.5f, 0.5f, 0.5f), + vec3(-0.5f, 0.5f, -0.5f) + }; + darray normals { + vec3(0.0f, 0.0f, -1.0f), + vec3(0.0f, 0.0f, -1.0f), + vec3(0.0f, 0.0f, -1.0f), + vec3(0.0f, 0.0f, -1.0f), + vec3(0.0f, 0.0f, -1.0f), + vec3(0.0f, 0.0f, -1.0f), + + vec3(0.0f, 0.0f, 1.0f), + vec3(0.0f, 0.0f, 1.0f), + vec3(0.0f, 0.0f, 1.0f), + vec3(0.0f, 0.0f, 1.0f), + vec3(0.0f, 0.0f, 1.0f), + vec3(0.0f, 0.0f, 1.0f), + + vec3(-1.0f, 0.0f, 0.0f), + vec3(-1.0f, 0.0f, 0.0f), + vec3(-1.0f, 0.0f, 0.0f), + vec3(-1.0f, 0.0f, 0.0f), + vec3(-1.0f, 0.0f, 0.0f), + vec3(-1.0f, 0.0f, 0.0f), + + vec3(1.0f, 0.0f, 0.0f), + vec3(1.0f, 0.0f, 0.0f), + vec3(1.0f, 0.0f, 0.0f), + vec3(1.0f, 0.0f, 0.0f), + vec3(1.0f, 0.0f, 0.0f), + vec3(1.0f, 0.0f, 0.0f), + + vec3(0.0f, -1.0f, 0.0f), + vec3(0.0f, -1.0f, 0.0f), + vec3(0.0f, -1.0f, 0.0f), + vec3(0.0f, -1.0f, 0.0f), + vec3(0.0f, -1.0f, 0.0f), + vec3(0.0f, -1.0f, 0.0f), + + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f) + }; + darray colors { + vec3(1.0f, 0.0f, 0.0f), + vec3(0.0f, 1.0f, 0.0f), + vec3(0.0f, 0.0f, 1.0f), + vec3(1.0f, 1.0f, 1.0f) + }; + darray> indices { + Vector(0, 1, 2), + Vector(2, 3, 0) + }; + + Entity entity {visual.shaderProgram()}; + entity.addVertices(vertices); + entity.addNormals(normals); + //entity.addColors(colors); + //entity.addIndices(indices); + + Scene scene; + scene.camera(new OrbitCamera()); + scene.add(TreeNode(entity)); + //Transform transform {&entity}; + + //gpu::DepthBuffer depthBuffer {true}; + visual.depthBuffer().bind(); + //gpu::CullFace cullFace {gpu::CullFace::Mode::FrontAndBack}; + float angle = 0; + glEnable(GL_PROGRAM_POINT_SIZE); + + /*gpu::ShaderProgram shaderProgram; + gpu::Shader vertexShader {gpu::Shader::Type::Vertex, gridVertexSource}; + vertexShader.create(); + + gpu::Shader fragmentShader {gpu::Shader::Type::Fragment, gridFragmentSource}; + fragmentShader.create(); + + shaderProgram.create(); + shaderProgram.attach(vertexShader); + shaderProgram.attach(fragmentShader); + shaderProgram.link(); + + shaderProgram.bind(); + shaderProgram.uniformMatrix<4, 4>("view", 1, true, scene.camera()->view().data()); + shaderProgram.uniformMatrix<4, 4>("projection", 1, true, scene.camera()->projection().data()); + shaderProgram.unbind();*/ + + /*Entity grid {visual.shaderProgram()}; + + darray vertices2; + auto genVert = [](float x, float y){; + return darray{ + vec3(-1.0f + x, 0.f + y, 0.f), + vec3(1.0f + x, 0.f + y, 0.f), + vec3(0.f + x, -1.0f + y, 0.f), + vec3(0.f + x, 1.0f + y, 0.f) + };}; + for (auto x = -100; x < 100; ++x) + for (auto y = -100; y < 100; ++y) + vertices2.push(genVert(x, y)); + grid.addVertices(vertices2); + grid.color(vec4(1, 1, 1, 1)); + grid.renderMode(gpu::ArrayObject::Lines);*/ + //Grid grid {visual.shaderProgram()}; + //scene.add(TreeNode(grid)); + /* + gpu::ArrayObject ao; + gpu::BufferObject bo; + ao.create(); + ao.bind(); + bo.create(vertices2); + bo.bind(); + ao.attribPointer(bo, 0, 3); + ao.unbind(); + bo.unbind();*/ + glfwWindowHint(GLFW_SAMPLES, 4); + ImPlot::CreateContext(); + glEnable(GL_MULTISAMPLE); + /*gpu::Texture tex; + tex.create(); + gpu::Renderbuffer rb; + rb.create();*/ + gpu::Framebuffer framebuffer; + framebuffer.create(); + gpu::Viewport viewport; + /*framebuffer.bind(); + framebuffer.attach(tex); + framebuffer.attach(rb); + framebuffer.unbind();*/ + const vec4 background = vec4(0.2f, 0.2f, 0.2f, 1.0f); + while (visual.window()->state() != gpu::Window::Closed) + { + + visual.colorBuffer().clear(background); + visual.depthBuffer().clear(); + //cullFace.bind(); + //entity.render(hpr::gpu::ArrayObject::Triangles); + //angle += 1; + //transform.rotate({0, 1, 0}, angle); + //transform.rotate({0, 0, 1}, angle); + //transform.render(); + viewport.size() = vec2(framebuffer.width(), framebuffer.height()); + viewport.set(); + framebuffer.bind(); + framebuffer.rescale(); + + visual.colorBuffer().clear(background); + visual.depthBuffer().clear(); + + scene.camera()->aspect() = (float)framebuffer.width() / (float)framebuffer.height(); + scene.render(); + framebuffer.unbind(); + viewport.size() = vec2((float)visual.window()->width(), (float)visual.window()->height()); + viewport.set(); + //shaderProgram.bind(); + + ui.createFrame(); + bool yes = true; + ImGui::ShowDemoWindow(&yes); + ImPlot::ShowDemoWindow(&yes); + + ImGui::Begin("Hello, world!"); + { + + if (ImGui::Button("Exit")) + visual.window()->state(gpu::Window::Closed); + ImGui::End(); + } + + ui.render(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + if (ImGui::Begin("Scene")) + { + ImGui::PopStyleVar(); + if (!ImGui::IsWindowCollapsed()) + { + framebuffer.width() = static_cast(ImGui::GetContentRegionAvail().x); + framebuffer.height() = static_cast(ImGui::GetContentRegionAvail().y); + + ImGui::Image(reinterpret_cast(framebuffer.texture().index()), ImGui::GetContentRegionAvail(), + ImVec2{0, 1}, ImVec2{1, 0}); + + ImGuiIO& io = ImGui::GetIO(); + Ray ray; + ray.fromScreen(io.MousePos.x, io.MousePos.y, framebuffer.width(), framebuffer.height(), scene.camera()); + float tMin, tMax; + Entity* ent = scene.nodes()[0].data(); + bool rayOBB = ray.obb({-1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 1.0f}, ent, tMin, tMax); + //std::cout << rayOBB << std::endl; + if (rayOBB) + ent->color(vec4(1.f, 0.f, 0.f, 1.f)); + else + ent->color(vec4(0.f, 1.f, 0.f, 1.f)); + if (ImGui::Begin("Properties")) + { + ImGui::DragFloat3("position", ray.p_position.data()); + ImGui::DragFloat3("direction", ray.p_direction.data()); + vec2 mouse {io.MousePos.x, io.MousePos.y}; + ImGui::DragFloat2("mouse", mouse.data()); + ImGui::Checkbox("ray", &rayOBB); + ImGui::DragFloat("dist", &tMin); + ImGui::DragFloat("dist", &tMax); + ImGui::End(); + } + if (ImGui::IsWindowHovered()) + { + if (io.WantCaptureMouse) + { + dynamic_cast(scene.camera())->scrollEvent(0, io.MouseWheel); + + if (ImGui::IsMouseDown(ImGuiMouseButton_Middle)) //(io.MouseDown[ImGuiMouseButton_Middle]) + { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) //(io.KeysDown[ImGuiKey_LeftShift]) + { + dynamic_cast(scene.camera())->moveEvent(io.MouseDelta.x, io.MouseDelta.y); + } + else + { + dynamic_cast(scene.camera())->rotateEvent(io.MouseDelta.x, io.MouseDelta.y); + } + } + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + + + } + } + } + } + ImGui::End(); + } + ui.renderFrame(); + visual.render(); + + } + ImPlot::DestroyContext(); + framebuffer.destroy(); + return 0; +} diff --git a/source/creator/transform.hpp b/source/creator/transform.hpp new file mode 100644 index 0000000..962595f --- /dev/null +++ b/source/creator/transform.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include +#include + +#include "entity.hpp" +#include +#include +#include + + +class Transform +{ + +public: + + enum Projection + { + Perspective, + Orthographic + }; + +protected: + + Projection p_projection; + hpr::scalar p_distance; + hpr::scalar p_aspect; + hpr::scalar p_fieldOfView; + hpr::scalar p_nearPlane; + hpr::scalar p_farPlane; + + hpr::vec3 p_target; + hpr::vec3 p_left; + hpr::vec3 p_front; + hpr::vec3 p_up; + hpr::vec3 p_rotation; + + hpr::mat4 p_model; + + Entity* p_entity; + + hpr::vec3 p_position; + +public: + + inline Transform() = delete; + + inline Transform(Entity* entity) : + p_projection {Orthographic}, + p_distance {10}, + p_aspect {800.f / 600}, + p_fieldOfView {45}, + p_nearPlane {0.1}, + p_farPlane {100}, + p_target {0, 0, 0}, + p_left {1, 0, 0}, + p_front {0, 0, -1}, + p_up {0, 0, 1}, + p_rotation {45, 45, 0}, + p_model {hpr::mat4::identity()}, + p_entity {entity}, + p_position {} + { + //for (auto v : p_model) + // std::cout << v << std::endl; + } + + hpr::mat4 view() + { + hpr::vec3 rotation { + hpr::cos(hpr::rad(p_rotation[1])) * hpr::sin(hpr::rad(p_rotation[0])), + hpr::cos(hpr::rad(p_rotation[1])) * hpr::cos(hpr::rad(p_rotation[0])), + hpr::sin(hpr::rad(p_rotation[1]))}; + hpr::vec3 pos = p_target + p_distance * rotation; + + p_front = p_target - pos; + p_front /= hpr::length(p_front); + + p_up = hpr::vec3(0, 0, 1); + p_left = hpr::normalize(hpr::cross(p_up, p_front)); + p_up = hpr::cross(p_front, p_left); + auto dd = glm::lookAt(glm::vec3(pos[0], pos[1], pos[2]), glm::vec3(pos[0] + p_front[0], pos[1] + p_front[1], pos[2] + p_front[2]), glm::vec3(p_up[0], p_up[1], p_up[2])); + auto dd2 = hpr::lookAt(pos, pos + p_front, p_up); + p_position = pos; + //for (auto v : dd2) + // std::cout << v << " "; + //std::cout << std::endl; + return dd2; + } +/* + -0.707107 0.707107 0 -0 + -0.5 -0.5 0.707107 -1.90735e-06 + 0.5 0.5 0.707107 -10 + 0 0 0 1 + */ + void rotateEvent(float xdelta, float ydelta) + { + p_rotation += hpr::vec3(xdelta, ydelta, 0); + } + + void moveEvent(float xdelta, float ydelta) + { + p_target += (p_left * xdelta + p_up * ydelta) * 1e-3f; + } + + void scrollEvent(float xdelta, float ydelta) + { + p_distance -= ydelta; + } + + hpr::mat4 projection(Projection projection) + { + p_projection = projection; + switch (p_projection) + { + case Perspective: + return hpr::perspective(hpr::rad(p_fieldOfView), p_aspect, p_nearPlane, p_farPlane); + case Orthographic: + return hpr::ortho(-p_distance * p_aspect, p_distance * p_aspect, -p_distance, p_distance, p_nearPlane, p_farPlane); + } + } + + void clear() + { + p_model = hpr::mat4::identity(); + } + + void translate(const hpr::vec3& position) + { + p_model = hpr::translate(p_model, position); + } + + void rotate(const hpr::vec3& axis, hpr::scalar angle) + { + p_model = hpr::rotate(p_model, axis, hpr::rad(angle)); + } + + void scale(const hpr::vec3& size) + { + p_model = hpr::scale(p_model, size); + } + /*struct Vec { + float x, y, z, w; + };*/ + void render() + { + //double model[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + //Vec model2[] = {Vec{0, 0, 0, 0}, Vec{0, 0, 0, 0},Vec{0, 0, 0, 0},Vec{0, 0, 0, 0}}; + //static_assert(sizeof(model2) == sizeof(float) * 16 && std::is_standard_layout_v); + //static_assert((sizeof(hpr::StaticArray::pointer) == (sizeof(GLfloat) * 16)) && + // (std::is_standard_layout::value), + // "Matrix4 does not satisfy contiguous storage requirements"); + p_entity->shaderProgram()->bind(); + + //glUniformMatrix4fv(glGetUniformLocation(p_entity->shaderProgram()->index(), "model"), + // 1, GL_FALSE, p_model.data()); + p_entity->shaderProgram()->uniformMatrix<4, 4, float>("model", 1, true, p_model.data()); + hpr::mat4 _view = view(); + p_entity->shaderProgram()->uniformMatrix<4, 4, float>("view", 1, true, _view.data()); + hpr::mat4 _projection = projection(p_projection); + + p_entity->shaderProgram()->uniformVector("viewPos", 1, p_position.data()); + + //for (auto v : _projection) + // std::cout << v << std::endl; + glm::mat4 _proj = glm::ortho(-p_distance * p_aspect, p_distance * p_aspect, -p_distance, p_distance, p_nearPlane, p_farPlane); + + p_entity->shaderProgram()->uniformMatrix<4, 4, float>("projection", 1, true, _projection.data()); + + hpr::vec3 objectColor {1.0f, 0.5f, 0.31f}; + p_entity->shaderProgram()->bind(); + p_entity->shaderProgram()->uniformVector("objectColor", 1, objectColor.data()); + p_entity->render(gpu::ArrayObject::Mode::Triangles); + + objectColor = {1.0f, 1.0f, 1.0f}; + p_entity->shaderProgram()->bind(); + p_entity->shaderProgram()->uniformVector("objectColor", 1, objectColor.data()); + p_entity->render(gpu::ArrayObject::Mode::LineLoop); + + p_entity->shaderProgram()->unbind(); + + clear(); + } +}; \ No newline at end of file diff --git a/source/creator/ui.hpp b/source/creator/ui.hpp new file mode 100644 index 0000000..005525a --- /dev/null +++ b/source/creator/ui.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include + +#include +#include +#include +#include +//#include + + +using namespace hpr; + +class UI +{ + + +public: + + UI(gpu::Window* window) + { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(window->instance(), true); + ImGui_ImplOpenGL3_Init("#version 430"); + io().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + } + + ~UI() + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + } + + void createFrame() + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + } + + void renderFrame() + { + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + + inline + ImGuiIO& io() + { + return ImGui::GetIO(); + } + + inline + void render() + { + static auto first_time = true; + static bool show_object_explorer = true; + static bool show_properties = true; + static bool show_demo = false; + + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("Options")) + { + ImGui::MenuItem("Undo", "CTRL+Z"); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("View")) + { + ImGui::MenuItem("Object explorer", nullptr, &show_object_explorer); + ImGui::MenuItem("Properties", nullptr, &show_properties); + ImGui::MenuItem("Demo", nullptr, &show_demo); + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + + if (show_demo) + ImGui::ShowDemoWindow(); + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar; // ImGuiWindowFlags_NoDocking; + window_flags |= ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + //window_flags |= ImGuiWindowFlags_MenuBar; + //window_flags |= ImGuiWindowFlags_NoBackground; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + + if (ImGui::Begin("Main dockspace", nullptr, window_flags)) + { + ImGui::PopStyleVar(3); + + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + + if (first_time) + { + first_time = false; + + ImGui::DockBuilderRemoveNode(dockspace_id); + ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); + + ImGuiID dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.2f, nullptr, &dockspace_id); + //ImGuiID dock_id_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 1.0f, &dockspace_id, nullptr); + ImGuiID dock_id_down = ImGui::DockBuilderSplitNode(dock_id_left, ImGuiDir_Down, 0.5f, nullptr, &dock_id_left); + + ImGuiDockNode* node = ImGui::DockBuilderGetNode(dockspace_id); + node->LocalFlags |= ImGuiDockNodeFlags_NoCloseButton; + + ImGui::DockBuilderDockWindow("Scene", dockspace_id); + ImGui::DockBuilderDockWindow("Object explorer", dock_id_left); + // Properties + node = ImGui::DockBuilderGetNode(dock_id_down); + node->LocalFlags |= ImGuiDockNodeFlags_NoCloseButton | ImGuiDockNodeFlags_NoTabBar; + ImGui::DockBuilderDockWindow("Properties", dock_id_down); + //ImGui::DockBuilderDockWindow("Scene_", dock_id_down); + ImGui::DockBuilderFinish(dockspace_id); + } + + ImGui::End(); + } + + ImGuiViewport* viewport2 = ImGui::GetMainViewport(); + ImGuiWindowFlags window_flags2 = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = ImGui::GetFrameHeight(); + + if (ImGui::BeginViewportSideBar("##SecondaryMenuBar", viewport2, ImGuiDir_Up, height, window_flags2)) { + if (ImGui::BeginMenuBar()) { + ImGui::Text("menu bar"); + ImGui::EndMenuBar(); + } + ImGui::End(); + } + + if (ImGui::BeginViewportSideBar("#MainStatusBar", viewport2, ImGuiDir_Down, height, window_flags2)) { + if (ImGui::BeginMenuBar()) { + ImGui::Text("status bar"); + ImGui::EndMenuBar(); + } + ImGui::End(); + } + } +}; \ No newline at end of file diff --git a/source/creator/visual.hpp b/source/creator/visual.hpp new file mode 100644 index 0000000..8e1802a --- /dev/null +++ b/source/creator/visual.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include "shaders.hpp" + +using namespace hpr; + +class Visual +{ + +protected: + + gpu::Window* p_window; + gpu::ShaderProgram* p_shaderProgram; + gpu::ColorBuffer p_colorBuffer; + gpu::DepthBuffer p_depthBuffer; + +public: + + Visual() : + p_window {new gpu::Window{50, 50, 1000, 800, "Hyporo", gpu::Window::Windowed, nullptr, nullptr}}, + p_shaderProgram {new gpu::ShaderProgram}, + p_colorBuffer {}, + p_depthBuffer {} + { + gpu::Shader vertexShader {gpu::Shader::Type::Vertex, vertexSource}; + vertexShader.create(); + + gpu::Shader fragmentShader {gpu::Shader::Type::Fragment, fragmentSource}; + fragmentShader.create(); + + p_shaderProgram->create(); + p_shaderProgram->attach(vertexShader); + p_shaderProgram->attach(fragmentShader); + p_shaderProgram->link(); + + vertexShader.destroy(); + fragmentShader.destroy(); + + p_window->framebufferResizeCallback([](gpu::Window* w, int width, int height){ + w->size(width, height); + }); + } + + ~Visual() + { + p_shaderProgram->destroy(); + delete p_shaderProgram; + p_window->destroy(); + delete p_window; + } + + gpu::Window* window() + { + return p_window; + } + + gpu::ShaderProgram* shaderProgram() + { + return p_shaderProgram; + } + + gpu::ColorBuffer& colorBuffer() + { + return p_colorBuffer; + } + + gpu::DepthBuffer& depthBuffer() + { + return p_depthBuffer; + } + + void render() + { + p_window->swapBuffers(); + p_window->pollEvents(); + } +}; \ No newline at end of file diff --git a/source/hpr/CMakeLists.txt b/source/hpr/CMakeLists.txt index c4c542b..3dadbb8 100644 --- a/source/hpr/CMakeLists.txt +++ b/source/hpr/CMakeLists.txt @@ -1,22 +1,39 @@ - -# Modules -add_subdirectory(core) -add_subdirectory(containers) -add_subdirectory(math) -add_subdirectory(io) -add_subdirectory(mesh) -add_subdirectory(geometry) - -if(WITH_CSG) - include(${CMAKE_SOURCE_DIR}/cmake/external/occt.cmake) - add_subdirectory(csg) -endif() -if(WITH_GPU) - include(${CMAKE_SOURCE_DIR}/cmake/external/glad.cmake) - include(${CMAKE_SOURCE_DIR}/cmake/external/stb.cmake) - add_subdirectory(gpu) -endif() -if(WITH_WINDOW_SYSTEM) - include(${CMAKE_SOURCE_DIR}/cmake/external/glfw.cmake) - add_subdirectory(window_system) -endif() + +# Modules and dependencies + +if (HPR_WITH_CONTAINERS) + add_subdirectory(containers) +endif() + +if (HPR_WITH_MATH) + add_subdirectory(math) +endif() + +if (HPR_WITH_IO) + add_subdirectory(io) +endif() + +if (HPR_WITH_MESH) + add_subdirectory(mesh) +endif() + +if (HPR_WITH_GEOMETRY) + add_subdirectory(geometry) +endif() + +if(HPR_WITH_CSG) + include(${HPR_EXTERNAL_PATH}/occt.cmake) + add_subdirectory(csg) +endif() + +if(HPR_WITH_GPU) + include(${HPR_EXTERNAL_PATH}/glad.cmake) + include(${HPR_EXTERNAL_PATH}/glfw.cmake) + include(${HPR_EXTERNAL_PATH}/stb.cmake) + add_subdirectory(gpu) +endif() + +if(HPR_WITH_PARALLEL) + include(${HPR_EXTERNAL_PATH}/onetbb.cmake) + add_subdirectory(parallel) +endif() diff --git a/source/hpr/containers.hpp b/source/hpr/containers.hpp index 1ea4bf1..86ccdd8 100644 --- a/source/hpr/containers.hpp +++ b/source/hpr/containers.hpp @@ -1,3 +1,3 @@ -#pragma once - -#include "containers/array.hpp" +#pragma once + +#include diff --git a/source/hpr/containers/CMakeLists.txt b/source/hpr/containers/CMakeLists.txt index 499e70e..502295d 100644 --- a/source/hpr/containers/CMakeLists.txt +++ b/source/hpr/containers/CMakeLists.txt @@ -1,62 +1,21 @@ -cmake_minimum_required(VERSION 3.16) - -project(containers - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX -) - -add_library(${PROJECT_NAME} INTERFACE) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../containers.hpp" "*.hpp" "array/*.hpp" -) - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ -) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) - - -if(HPR_TEST) - add_subdirectory(tests) -endif() \ No newline at end of file +cmake_minimum_required(VERSION 3.16) + +project(containers + VERSION "${HPR_VERSION}" + LANGUAGES CXX +) + +hpr_add_library(${PROJECT_NAME} INTERFACE) + +hpr_collect_interface(${PROJECT_NAME} + "../containers.hpp" + "*.hpp" + "array/*.hpp" +) + +target_sources(${PROJECT_NAME} + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> +) + +hpr_tests(${PROJECT_NAME} tests) +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) \ No newline at end of file diff --git a/source/hpr/containers/array.hpp b/source/hpr/containers/array.hpp index 39bc1c7..d9bc6f0 100644 --- a/source/hpr/containers/array.hpp +++ b/source/hpr/containers/array.hpp @@ -1,4 +1,5 @@ -#pragma once - -#include "array/dynamic_array.hpp" -#include "array/static_array.hpp" \ No newline at end of file +#pragma once + +#include +#include +#include \ No newline at end of file diff --git a/source/hpr/containers/array/dynamic_array.hpp b/source/hpr/containers/array/dynamic_array.hpp index 5359c11..916ade0 100644 --- a/source/hpr/containers/array/dynamic_array.hpp +++ b/source/hpr/containers/array/dynamic_array.hpp @@ -1,427 +1,297 @@ -#pragma once - -#include "iterator.hpp" -#include -#include -#include - - -namespace hpr -{ - -template -class DynamicArray -{ - -public: - - using difference_type = std::ptrdiff_t; - using value_type = Type; - using size_type = size_t; - using pointer = Type*; - using reference = Type&; - using iterator = Iterator; - using const_pointer = Type const*; - using const_reference = Type const&; - using const_iterator = Iterator; - -protected: - - size_type p_size; - size_type p_capacity; - pointer p_start; - pointer p_end; - pointer p_storage_end; - -public: - - inline - DynamicArray() : - p_size {0}, - p_capacity {1}, - p_start {new value_type[p_capacity]}, - p_end {p_start}, - p_storage_end {p_end + p_capacity} - {} - - inline - DynamicArray(const DynamicArray& arr) : - p_size {arr.p_size}, - p_capacity {arr.p_capacity}, - p_start {new value_type[arr.p_size]}, - p_end {p_start + p_size}, - p_storage_end {p_start + p_capacity} - { - std::copy(arr.p_start, arr.p_end, p_start); - } - - //! Move constructor - inline - DynamicArray(DynamicArray&& arr) noexcept : - DynamicArray {} - { - swap(*this, arr); - } - - inline - DynamicArray(const_iterator start, const_iterator end) : - p_size {size_type(end.operator->() - start.operator->())}, - p_capacity {p_size}, - p_start {new value_type[p_size]}, - p_end {p_start + p_size}, - p_storage_end {p_start + p_capacity} - { - std::copy(start, end, p_start); - } - - inline - DynamicArray(iterator start, iterator end) : - p_size {size_type(end.operator->() - start.operator->())}, - p_capacity {p_size}, - p_start {new value_type[p_size]}, - p_end {p_start + p_size}, - p_storage_end {p_start + p_capacity} - { - std::copy(start, end, p_start); - } - - inline - DynamicArray(std::initializer_list list) : - p_size {list.size()}, - p_capacity {list.size()}, - p_start {new value_type[p_capacity]}, - p_end {p_start + p_size}, - p_storage_end {p_start + p_capacity} - { - std::copy(list.begin(), list.end(), p_start); - } - - inline - DynamicArray(size_type size, value_type value) : - p_size {0}, - p_capacity {size}, - p_start {new value_type[p_capacity]}, - p_end {p_start + p_size}, - p_storage_end {p_start + p_capacity} - { - for (auto n = 0; n < size; ++n) - push(value); - } - - inline - DynamicArray& operator=(const DynamicArray& vs) noexcept - { - delete[] p_start; - p_size = vs.p_size; - p_capacity = vs.p_size; - p_start = new value_type[p_capacity]; - p_end = p_start + p_size; - p_storage_end = p_start + p_capacity; - std::copy(vs.begin(), vs.end(), begin()); - return *this; - } - - inline - DynamicArray& operator=(DynamicArray&& arr) noexcept - { - swap(*this, arr); - return *this; - } - - virtual - ~DynamicArray() - { - //std::destroy(p_start, p_end); - delete[] p_start; - } - - // Member functions - - virtual - iterator begin() - { - return iterator(p_start); - } - - [[nodiscard]] virtual - const_iterator begin() const - { - return const_iterator(p_start); - } - - virtual - iterator end() - { - return iterator(p_end); - } - - virtual - const_iterator end() const - { - return const_iterator(p_end); - } - - - [[nodiscard]] virtual - size_type size() const - { - return size_type(p_end - p_start); - } - - [[nodiscard]] virtual - size_type capacity() const - { - return size_type(p_storage_end - p_start); - } - - [[nodiscard]] virtual - bool is_empty() const - { - return begin() == end(); - } - - virtual - reference operator[](size_type n) - { - if (n >= size()) - throw std::out_of_range("Index out of bounds"); - return *(p_start + n); - } - - virtual - const_reference operator[](size_type n) const - { - if (n >= size()) - throw std::out_of_range("Index out of bounds"); - return *(p_start + n); - } - - virtual - reference front() - { - return *p_start; - } - - virtual - reference back() - { - return *(p_end - 1); - } - - virtual - pointer data() - { - return p_start; - } - - [[nodiscard]] virtual - const_pointer data() const - { - return p_start; - } - - virtual - void resize(size_type newCapacity) - { - if (newCapacity == p_capacity) - return; - if (std::numeric_limits::max() - size() < newCapacity) - throw std::length_error("Wrong capacity value passed (possibly negative)"); - - pointer newStart = new value_type[newCapacity]; - - if (newCapacity > p_capacity) - { - std::move(p_start, p_end, newStart); - } - else if (newCapacity < p_capacity) - { - if (newCapacity < p_size) { - std::move(p_start, p_start + newCapacity, newStart); - p_size = newCapacity; - } - else - { - std::move(p_start, p_end, newStart); - } - } - - delete[] p_start; - std::swap(p_start, newStart); - p_capacity = newCapacity; - p_end = p_start + p_size; - p_storage_end = p_start + p_capacity; - } - - virtual - void push(const value_type& val) - { - if (p_end == p_storage_end) - resize(p_capacity * 2); - *p_end = val; - ++p_end; - ++p_size; - } - - virtual - void push(value_type&& val) - { - if (p_end == p_storage_end) - resize(p_capacity * 2); - *p_end = std::move(val); - ++p_end; - ++p_size; - } - - virtual - value_type pop() - { - if (is_empty()) - throw std::length_error("Cannot pop element from empty array"); - value_type val = back(); - std::destroy_at(p_end); - --p_end; - --p_size; - return val; - } - - virtual - void insert(size_type position, const value_type& val) - { - if (p_end == p_storage_end) - resize(p_capacity * 2); - for (size_type n = p_size; n > position; --n) - *(p_start + n) = std::move(*(p_start + n - 1)); - *(p_start + position) = val; - ++p_size; - ++p_end; - } - - virtual - void insert(size_type position, value_type&& val) - { - if (p_end == p_storage_end) - resize(p_capacity * 2); - for (size_type n = p_size; n > position; --n) - *(p_start + n) = std::move(*(p_start + n - 1)); - *(p_start + position) = std::move(val); - ++p_size; - ++p_end; - } - - /*virtual - int findByAddress(const value_type& value) - { - // TODO: make better approach - for (int n = 0; n < p_size; ++n) { - std::equal_to - pointer lhs = (p_start + n); //*std::addressof(*(p_start + n)); - pointer rhs = *value; //*std::addressof(value); - if (lhs == rhs) - return n; - } - return -1; - }*/ - - virtual - void remove(size_type position) - { - for (size_type n = position; n < p_size - 1; ++n) - *(p_start + n) = std::move(*(p_start + n + 1)); - std::destroy_at(p_end); - --p_end; - --p_size; - } - - virtual - void remove(iterator position) - { - if (position + 1 != end()) - std::copy(position + 1, end(), position); - std::destroy_at(p_end); - --p_end; - --p_size; - } - - virtual - void remove(const std::function& condition) - { - size_type newSize = p_size; - for (size_type offset = 0; offset < newSize; ++offset) - if (condition(*(p_start + offset))) { - for (size_type n = offset; n < newSize; ++n) { - *(p_start + n) = std::move(*(p_start + n + 1)); - } - --newSize; - --offset; - } - p_size = newSize; - p_end = p_start + p_size; - } - - /*virtual - void remove(const value_type& value) - { - int index = findByAddress(value); - if (index != -1) - remove(index); - else - throw std::runtime_error("Value is not found to remove it"); - }*/ - - virtual - void clear() - { - delete[] p_start; - p_size = 0; - p_capacity = 1; - p_start = new value_type[p_capacity]; - p_end = p_start; - p_storage_end = p_end + p_capacity; - } - - DynamicArray slice(iterator start, iterator end) - { - return DynamicArray {start, end}; - } - - // Friend functions - - friend - void swap(DynamicArray& lhs, DynamicArray& rhs) - { - std::swap(lhs.p_size, rhs.p_size); - std::swap(lhs.p_capacity, rhs.p_capacity); - std::swap(lhs.p_start, rhs.p_start); - std::swap(lhs.p_end, rhs.p_end); - std::swap(lhs.p_storage_end, rhs.p_storage_end); - } - - friend - bool operator==(const DynamicArray& lhs, const DynamicArray& rhs) - { - for (auto n = 0; n < lhs.size(); ++n) - if (lhs[n] != rhs[n]) - return false; - return true; - } - - friend - DynamicArray operator+(const DynamicArray& lhs, const DynamicArray& rhs) - { - DynamicArray arr {rhs.size() + lhs.size(), {}}; - for (auto n = 0; n < rhs.size(); ++n) - { - arr[n] = rhs[n]; - arr[n + rhs.size()] = lhs[n]; - } - return arr; - } -}; - -// Aliases - -template -using darray = DynamicArray; - +#pragma once + +#include + +#include +#include + + +namespace hpr +{ + +// forward declaration + +template +class DynamicArray; + +// type traits + +template +struct is_sequence> : public std::true_type +{}; + +// aliases + +template +using darray = DynamicArray; + +// class definition + +template +class DynamicArray : public Sequence +{ + + using base = Sequence; + +public: + + using difference_type = std::ptrdiff_t; + using value_type = T; + using size_type = Size; + using pointer = T*; + using reference = T&; + using iterator = Iterator; + using const_pointer = T const*; + using const_reference = T const&; + using const_iterator = Iterator; + +public: + + friend constexpr + void swap(DynamicArray& main, DynamicArray& other) noexcept + { + using std::swap; + swap(static_cast(main), static_cast(other)); + } + + constexpr + DynamicArray() noexcept : + base {} + {} + + constexpr explicit + DynamicArray(const base& b) noexcept : + base {b} + {} + + constexpr + DynamicArray(const DynamicArray& arr) noexcept : + base {static_cast(arr)} + {} + + //! Move constructor + constexpr + DynamicArray(DynamicArray&& arr) noexcept : + base {std::forward(static_cast(arr))} + {} + + template + constexpr + DynamicArray(Iter start, Iter end) : + base {start, end} + {} + + constexpr + DynamicArray(typename base::iterator start, typename base::iterator end) : + base {start, end} + {} + + constexpr + DynamicArray(typename base::const_iterator start, typename base::const_iterator end) : + base {start, end} + {} + + constexpr + DynamicArray(typename base::iterator start, typename base::iterator end, size_type capacity) : + base {start, end, capacity} + {} + + constexpr + DynamicArray(typename base::const_iterator start, typename base::const_iterator end, size_type capacity) : + base {start, end, capacity} + {} + + constexpr + DynamicArray(std::initializer_list list) : + base {list.begin(), list.end()} + {} + + constexpr + DynamicArray(size_type size, value_type value) noexcept : + base {size, value} + {} + + constexpr + DynamicArray& operator=(DynamicArray other) noexcept + { + swap(*this, other); + return *this; + } + + constexpr + DynamicArray& operator=(DynamicArray&& arr) noexcept + { + swap(*this, arr); + return *this; + } + + virtual + ~DynamicArray() = default; + + // Member functions + + [[nodiscard]] virtual constexpr + size_type capacity() const noexcept + { + return base::p_capacity; + } + + [[nodiscard]] virtual constexpr + bool is_empty() const + { + return base::begin() == base::end(); + } + + virtual constexpr + iterator storage_end() + { + return iterator(base::p_start + base::p_capacity); + } + + virtual constexpr + const_iterator storage_end() const + { + return const_iterator(base::p_start + base::p_capacity); + } + + virtual constexpr + void resize(size_type newCapacity) + { + if (newCapacity == base::p_capacity) + return; + + if (std::numeric_limits::max() - base::size() < newCapacity) + throw hpr::LengthError("Invalid capacity value passed"); + + if (newCapacity > base::p_capacity) + { + DynamicArray tmp {base::begin(), base::end(), newCapacity}; + swap(*this, tmp); + } + else + { + if (newCapacity >= base::p_size) + { + DynamicArray tmp {base::begin(), base::end()}; + swap(*this, tmp); + } + else + { + DynamicArray tmp {base::begin(), base::begin() + newCapacity}; + swap(*this, tmp); + } + } + } + + virtual constexpr + void push(const value_type& val) + { + if (base::end() == storage_end()) + resize(base::p_capacity * 2); + *base::end() = val; + ++base::p_size; + } + + virtual constexpr + void push(value_type&& val) + { + if (base::end() == storage_end()) + resize(base::p_capacity * 2); + *base::end() = std::move(val); + ++base::p_size; + } + + virtual constexpr + void push(const DynamicArray& arr) + { + for (const value_type& el : arr) + push(el); + } + + virtual constexpr + value_type pop() + { + if (is_empty()) + throw hpr::LengthError("Cannot pop element from empty array"); + value_type val = base::back(); + std::destroy_at(base::p_start + base::p_size); + --base::p_size; + return val; + } + + virtual constexpr + void insert(size_type position, const value_type& val) + { + if (base::end() == storage_end()) + resize(base::p_capacity * 2); + for (size_type n = base::p_size; n > position; --n) + *(base::p_start + n) = std::move(*(base::p_start + n - 1)); + *(base::p_start + position) = val; + ++base::p_size; + } + + virtual constexpr + void insert(size_type position, value_type&& val) + { + if (base::end() == storage_end()) + resize(base::p_capacity * 2); + for (size_type n = base::p_size; n > position; --n) + *(base::p_start + n) = std::move(*(base::p_start + n - 1)); + *(base::p_start + position) = std::move(val); + ++base::p_size; + } + + virtual constexpr + void remove(size_type position) + { + for (size_type n = position; n < base::p_size - 1; ++n) + *(base::p_start + n) = std::move(*(base::p_start + n + 1)); + std::destroy_at(base::p_start + base::p_size); + --base::p_size; + } + + virtual constexpr + void remove(iterator position) + { + if (position + 1 != base::end()) + std::copy(position + 1, base::end(), position); + std::destroy_at(base::p_start + base::p_size); + --base::p_size; + } + + virtual constexpr + void remove(const std::function& condition) + { + size_type newSize = base::p_size; + for (size_type offset = 0; offset < newSize; ++offset) + { + if (condition(*(base::p_start + offset))) + { + for (size_type n = offset; n < newSize; ++n) + *(base::p_start + n) = std::move(*(base::p_start + n + 1)); + + --newSize; + --offset; + } + } + base::p_size = newSize; + } + + virtual constexpr + void clear() + { + delete[] base::p_start; + base::p_size = 0; + base::p_capacity = 1; + base::p_start = new value_type[base::p_capacity]; + } + + DynamicArray slice(iterator start, iterator end) + { + return DynamicArray {start, end}; + } + +}; + + } \ No newline at end of file diff --git a/source/hpr/containers/array/iterator.hpp b/source/hpr/containers/array/iterator.hpp index 67f815c..42fd721 100644 --- a/source/hpr/containers/array/iterator.hpp +++ b/source/hpr/containers/array/iterator.hpp @@ -1,95 +1,98 @@ -#pragma once - -#include - - -namespace hpr -{ - -template -class Iterator -{ -public: - using iterator_category = Category; - using difference_type = std::ptrdiff_t; - using value_type = Type; - using pointer = Type*; - using reference = Type&; - using iterator = Iterator; - using const_pointer = Type const*; - using const_reference = Type const&; - using const_iterator = Iterator const; - -protected: - pointer p_ptr; - -public: - - Iterator() : - p_ptr {nullptr} - {} - - Iterator(pointer ptr) : - p_ptr {ptr} - {} - - reference operator*() - { - return *p_ptr; - } - - const_reference operator*() const - { - return *p_ptr; - } - - pointer operator->() - { - return p_ptr; - } - - const_pointer operator->() const - { - return p_ptr; - } - - iterator& operator++() - { - p_ptr++; - return *this; - } - - const_iterator& operator++() const - { - p_ptr++; - return *this; - } - - iterator operator++(int) - { - iterator temp {*this}; - ++(*this); - return temp; - } - - iterator operator+(int value) - { - iterator temp {*this}; - temp.p_ptr += value; - return temp; - } - - friend - bool operator==(const iterator& lhs, const iterator& rhs) - { - return lhs.p_ptr == rhs.p_ptr; - } - - friend - bool operator!=(const iterator& lhs, const iterator& rhs) - { - return lhs.p_ptr != rhs.p_ptr; - } -}; - -} +#pragma once + +#include + + +namespace hpr +{ + +template +class Iterator +{ + +public: + + using iterator_category = Category; + using difference_type = std::ptrdiff_t; + using value_type = Type; + using pointer = Type*; + using reference = Type&; + using iterator = Iterator; + using const_pointer = Type const*; + using const_reference = Type const&; + using const_iterator = Iterator const; + +protected: + + pointer p_ptr; + +public: + + Iterator() : + p_ptr {nullptr} + {} + + Iterator(pointer ptr) : + p_ptr {ptr} + {} + + reference operator*() + { + return *p_ptr; + } + + const_reference operator*() const + { + return *p_ptr; + } + + pointer operator->() + { + return p_ptr; + } + + const_pointer operator->() const + { + return p_ptr; + } + + iterator& operator++() + { + p_ptr++; + return *this; + } + + const_iterator& operator++() const + { + p_ptr++; + return *this; + } + + iterator operator++(int) + { + iterator temp {*this}; + ++(*this); + return temp; + } + + iterator operator+(int value) + { + iterator temp {*this}; + temp.p_ptr += value; + return temp; + } + + friend + bool operator==(const iterator& lhs, const iterator& rhs) + { + return lhs.p_ptr == rhs.p_ptr; + } + + friend + bool operator!=(const iterator& lhs, const iterator& rhs) + { + return lhs.p_ptr != rhs.p_ptr; + } +}; + +} diff --git a/source/hpr/containers/array/sequence.hpp b/source/hpr/containers/array/sequence.hpp new file mode 100644 index 0000000..2df9665 --- /dev/null +++ b/source/hpr/containers/array/sequence.hpp @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include + +namespace hpr +{ + +// forward declaration + +template +class Sequence; + +// type traits + +template +struct is_sequence : public std::false_type +{}; + +template +struct is_sequence> : public std::true_type +{}; + +// class definition + +template +class Sequence +{ + +public: + + using difference_type = std::ptrdiff_t; + using value_type = T; + using size_type = Size; + using pointer = T*; + using reference = T&; + using iterator = Iterator; + using const_pointer = T const*; + using const_reference = T const&; + using const_iterator = Iterator; + +protected: + + size_type p_size; + size_type p_capacity; + pointer p_start; + +protected: + + constexpr + Sequence(size_type size, size_type capacity, pointer start) : + p_size {size}, + p_capacity {capacity}, + p_start {start} + {} + + constexpr explicit + Sequence(size_type size, size_type capacity) : + p_size {size}, + p_capacity {capacity}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + {} + +public: + + friend constexpr + void swap(Sequence& main, Sequence& other) noexcept + { + using std::swap; + swap(main.p_size, other.p_size); + swap(main.p_capacity, other.p_capacity); + swap(main.p_start, other.p_start); + } + + constexpr + Sequence() noexcept : + p_size {0}, + p_capacity {1}, + p_start {new value_type[p_capacity]} + {} + + constexpr + Sequence(const Sequence& seq) noexcept : + p_size {seq.p_size}, + p_capacity {seq.p_capacity}, + p_start {p_capacity ? new value_type[seq.p_capacity] : nullptr} + { + std::copy(seq.p_start, seq.p_start + seq.p_size, p_start); + } + + //! Move constructor + constexpr + Sequence(Sequence&& seq) noexcept : + Sequence {} + { + swap(*this, seq); + } + + template + constexpr + Sequence(Iter start, Iter end) : + p_size {static_cast(std::distance(start, end))},//size_type(end.operator->() - start.operator->())}, + p_capacity {p_size}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + std::copy(start, end, p_start); + } + + constexpr + Sequence(iterator start, iterator end) : + p_size {static_cast(std::distance(start, end))},//size_type(end.operator->() - start.operator->())}, + p_capacity {p_size}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + std::copy(start, end, p_start); + } + + constexpr + Sequence(const_iterator start, const_iterator end) : + p_size {std::distance(start, end)},//{size_type(end.operator->() - start.operator->())}, + p_capacity {p_size}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + std::copy(start, end, p_start); + } + + constexpr + Sequence(iterator start, iterator end, size_type capacity) : + p_size {static_cast(std::distance(start, end))},//size_type(end.operator->() - start.operator->())}, + p_capacity {capacity}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + std::copy(start, end, p_start); + } + + constexpr + Sequence(const_iterator start, const_iterator end, size_type capacity) : + p_size {std::distance(start, end)},//{size_type(end.operator->() - start.operator->())}, + p_capacity {capacity}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + std::copy(start, end, p_start); + } + + /*constexpr + Sequence(std::initializer_list list) : + Sequence {list.begin(), list.end()} + {}*/ + + constexpr + Sequence(size_type size, value_type value) noexcept: + p_size {size}, + p_capacity {size}, + p_start {p_capacity ? new value_type[p_capacity] : nullptr} + { + for (auto n = 0; n < size; ++n) + *(p_start + n) = value; + } + + constexpr + Sequence& operator=(Sequence other) noexcept + { + swap(*this, other); + return *this; + } + + constexpr + Sequence& operator=(Sequence&& seq) noexcept + { + swap(*this, seq); + return *this; + } + + virtual + ~Sequence() + { + delete[] p_start; + } + + + // Member functions + + virtual constexpr + iterator begin() + { + return iterator(p_start); + } + + [[nodiscard]] virtual constexpr + const_iterator begin() const + { + return const_iterator(p_start); + } + + virtual constexpr + iterator end() + { + return iterator(p_start + p_size); + } + + virtual constexpr + const_iterator end() const + { + return const_iterator(p_start + p_size); + } + + virtual constexpr + iterator storage_end() + { + return iterator(p_start + p_capacity); + } + + virtual constexpr + const_iterator storage_end() const + { + return const_iterator(p_start + p_capacity); + } + + [[nodiscard]] virtual constexpr + size_type size() const noexcept + { + return p_size; + } + + virtual constexpr + reference operator[](size_type n) + { + if (n >= size()) + throw hpr::OutOfRange(); + return *(p_start + n); + } + + virtual constexpr + const_reference operator[](size_type n) const + { + if (n >= size()) + throw hpr::OutOfRange(); + return *(p_start + n); + } + + virtual constexpr + reference at(size_type n) + { + return operator[](n); + } + + virtual constexpr + const_reference at(size_type n) const + { + return operator[](n); + } + + virtual constexpr + reference front() + { + return *begin(); + } + + virtual constexpr + const_reference front() const + { + return *begin(); + } + + virtual constexpr + reference back() + { + return *(p_start + p_size - 1); + } + + virtual constexpr + const_reference back() const + { + return *(p_start + p_size - 1); + } + + virtual constexpr + pointer data() + { + return p_start; + } + + [[nodiscard]] virtual constexpr + const_pointer data() const + { + return p_start; + } + + // Friend functions + + friend + bool operator==(const Sequence& lhs, const Sequence& rhs) + { + for (auto n = 0; n < lhs.size(); ++n) + if (lhs[n] != rhs[n]) + return false; + return true; + } + + friend + Sequence operator+(const Sequence& lhs, const Sequence& rhs) + { + Sequence seq {rhs.size() + lhs.size(), {}}; + for (auto n = 0; n < rhs.size(); ++n) + { + seq[n] = rhs[n]; + seq[n + rhs.size()] = lhs[n]; + } + return seq; + } +}; + +} \ No newline at end of file diff --git a/source/hpr/containers/array/static_array.hpp b/source/hpr/containers/array/static_array.hpp index 76783d6..da2ca54 100644 --- a/source/hpr/containers/array/static_array.hpp +++ b/source/hpr/containers/array/static_array.hpp @@ -1,257 +1,150 @@ -#pragma once - -#include "iterator.hpp" - -#include -#include -#include - - -namespace hpr -{ - -template -class StaticArray -{ - -public: - - using difference_type = std::ptrdiff_t; - using value_type = Type; - using size_type = size_t; - using pointer = Type*; - using reference = Type&; - using iterator = Iterator; - using const_pointer = Type const*; - using const_reference = Type const&; - using const_iterator = Iterator; - -protected: - - const size_type p_size; - pointer p_start; - pointer p_end; - -protected: - - inline - StaticArray(size_type size, pointer start, pointer end) : - p_size {size}, - p_start {start}, - p_end {end} - {} - -public: - - inline - StaticArray() : - p_size {Size}, - p_start {new value_type[p_size] {}}, - p_end {p_start + p_size} - {} - - inline - StaticArray(const StaticArray& arr) : - p_size {arr.p_size}, - p_start {new value_type[arr.p_size]}, - p_end {p_start + p_size} - { - std::copy(arr.p_start, arr.p_end, p_start); - } - - //! Move constructor - inline - StaticArray(StaticArray&& arr) noexcept : - StaticArray {} - { - swap(*this, arr); - } - - inline - StaticArray(const_iterator start, const_iterator end) : - p_size {Size}, - p_start {new value_type[p_size]}, - p_end {p_start + p_size} - { - std::copy(start, end, p_start); - } - - inline - StaticArray(iterator start, iterator end) : - p_size {Size}, - p_start {new value_type[p_size]}, - p_end {p_start + p_size} - { - std::copy(start, end, p_start); - } - - inline - StaticArray(std::initializer_list list) : - StaticArray {list.begin(), list.end()} - {} - - template ... Args> - inline - StaticArray(const value_type& v, const Args& ...args) : - StaticArray {std::initializer_list({std::forward(v), - std::forward(static_cast(args))...})} - { - static_assert(1 + sizeof...(args) == Size, "Number of arguments must be equal to size of array"); - } - - template ... Args> - inline - StaticArray(value_type&& v, Args&& ...args) : - StaticArray {std::initializer_list({std::forward(v), - std::forward(static_cast(args))...})} - { - static_assert(1 + sizeof...(args) == Size, "Number of arguments must be equal to size of array"); - } - - template ... Args> - inline - StaticArray(const StaticArray& subarr, const value_type& v, const Args& ...args) : - p_size {Size}, - p_start {new value_type[p_size]}, - p_end {p_start + p_size} - { - static_assert(SubSize + 1 + sizeof...(args) == Size, - "Number of arguments and sub-array elements must be equal to size of main array"); - std::initializer_list list {v, static_cast(args)...}; - std::copy(subarr.begin(), subarr.end(), begin()); - size_type pos {subarr.size()}; - for (auto& val : list) - (*this)[pos++] = val; - } - - inline - StaticArray& operator=(const StaticArray& vs) - { - std::copy(vs.begin(), vs.end(), begin()); - return *this; - } - - inline - StaticArray& operator=(StaticArray&& arr) noexcept - { - swap(*this, arr); - return *this; - } - - virtual - ~StaticArray() - { - //std::destroy(p_start, p_end); - delete[] p_start; - } - - // Member functions - - virtual - iterator begin() - { - return iterator(p_start); - } - - [[nodiscard]] virtual - const_iterator begin() const - { - return const_iterator(p_start); - } - - virtual - iterator end() - { - return iterator(p_end); - } - - virtual - const_iterator end() const - { - return const_iterator(p_end); - } - - [[nodiscard]] virtual constexpr - size_type size() const - { - return size_type(p_end - p_start); - } - - [[nodiscard]] virtual - bool is_empty() const - { - return begin() == end(); - } - - virtual - reference operator[](size_type n) - { - if (n >= size()) - throw std::out_of_range("Index out of bounds"); - return *(p_start + n); - } - - virtual - const_reference operator[](size_type n) const - { - if (n >= size()) - throw std::out_of_range("Index out of bounds"); - return *(p_start + n); - } - - virtual - reference front() - { - return *p_start; - } - - virtual - reference back() - { - return *(p_end - 1); - } - - virtual - pointer data() - { - return p_start; - } - - [[nodiscard]] virtual - const_pointer data() const - { - return p_start; - } - - // Friend functions - - friend - void swap(StaticArray& lhs, StaticArray& rhs) - { - std::swap(lhs.p_start, rhs.p_start); - std::swap(lhs.p_end, rhs.p_end); - } - - friend - void swap(StaticArray&& lhs, StaticArray&& rhs) - { - std::swap(lhs.p_start, rhs.p_start); - std::swap(lhs.p_end, rhs.p_end); - } - - friend - bool operator==(const StaticArray& lhs, const StaticArray& rhs) - { - for (auto n = 0; n < Size; ++n) - if (lhs[n] != rhs[n]) - return false; - return true; - } -}; - -// Aliases - -template -using sarray = StaticArray; - -} +#pragma once + +#include + + +namespace hpr +{ + +// forward declaration + +template +requires (S > 0) +class StaticArray; + +// type traits + +template +struct is_sequence> : public std::true_type +{}; + +// aliases + +template +using sarray = StaticArray; + +// class definition + +template +requires (S > 0) +class StaticArray : public Sequence +{ + + using base = Sequence; + +public: + + using difference_type = std::ptrdiff_t; + using value_type = T; + using size_type = Size; + using pointer = T*; + using reference = T&; + using iterator = Iterator; + using const_pointer = T const*; + using const_reference = T const&; + using const_iterator = Iterator; + +public: + + friend constexpr + void swap(StaticArray& main, StaticArray& other) noexcept + { + using std::swap; + swap(static_cast(main), static_cast(other)); + } + + //! Default constructor + constexpr + StaticArray() noexcept : + base {S, S, new value_type[S] {}} + {} + + constexpr explicit + StaticArray(const base& b) noexcept : + base {b} + {} + + //! Copy constructor + constexpr + StaticArray(const StaticArray& arr) noexcept : + base {static_cast(arr)} + {} + + //! Move constructor + constexpr + StaticArray(StaticArray&& arr) noexcept : + base {std::forward(static_cast(arr))} + {} + + constexpr + StaticArray(typename base::iterator start, typename base::iterator end) : + base {start, end} + {} + + constexpr + StaticArray(typename base::const_iterator start, typename base::const_iterator end) : + base {start, end} + {} + + constexpr + StaticArray(typename base::iterator start, typename base::iterator end, size_type capacity) : + base {start, end, capacity} + {} + + constexpr + StaticArray(typename base::const_iterator start, typename base::const_iterator end, size_type capacity) : + base {start, end, capacity} + {} + + constexpr + StaticArray(std::initializer_list list) : + base {list.begin(), list.end()} + {} + + template ... Args> + constexpr explicit + StaticArray(const value_type& v, const Args& ...args) + requires (1 + sizeof...(args) == S) : + StaticArray {std::initializer_list({v, static_cast(args)...})} + {} + + template ... Args> + constexpr explicit + StaticArray(value_type&& v, Args&& ...args) + requires (1 + sizeof...(args) == S) : + StaticArray {std::initializer_list({std::forward(v), std::forward(static_cast(args))...})} + {} + + template ... Args> + constexpr + StaticArray(const StaticArray& subArr, const value_type& v, const Args& ...args) noexcept + requires (SS + 1 + sizeof...(args) == S) : + base {S, S, new value_type[S] {}} + { + std::copy(subArr.begin(), subArr.end(), base::begin()); + std::initializer_list list {v, static_cast(args)...}; + std::copy(list.begin(), list.end(), base::begin() + subArr.size()); + } + + constexpr + StaticArray& operator=(StaticArray other) + { + swap(*this, other); + return *this; + } + + constexpr + StaticArray& operator=(StaticArray&& other) noexcept + { + swap(*this, other); + return *this; + } + + virtual + ~StaticArray() = default; + + +}; + + +} diff --git a/source/hpr/containers/graph.hpp b/source/hpr/containers/graph.hpp new file mode 100644 index 0000000..d951bd5 --- /dev/null +++ b/source/hpr/containers/graph.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include \ No newline at end of file diff --git a/source/hpr/containers/graph/tree_node.hpp b/source/hpr/containers/graph/tree_node.hpp new file mode 100644 index 0000000..67d7108 --- /dev/null +++ b/source/hpr/containers/graph/tree_node.hpp @@ -0,0 +1,180 @@ +#pragma once + +#include +#include + + +namespace hpr +{ + +template +class TreeNode +{ + +public: + + using value_type = Type; + using pointer = Type*; + using reference = Type&; + using const_pointer = Type const*; + using const_reference = Type const&; + +protected: + + pointer p_data; + + darray p_descendants; + TreeNode* p_ancestor; + +public: + + friend constexpr + void swap(TreeNode& main, TreeNode& other) noexcept + { + using std::swap; + swap(main.p_data, other.p_data); + swap(main.p_descendants, other.p_descendants); + swap(main.p_ancestor, other.p_ancestor); + } + + inline + TreeNode() : + p_data {new value_type {}}, + p_descendants {}, + p_ancestor {} + {} + + inline + TreeNode(const TreeNode& node) : + p_data {new value_type {*node.p_data}}, + p_descendants {}, + p_ancestor {} + {} + + inline + TreeNode(TreeNode&& node) noexcept : + p_data {}, + p_descendants {}, + p_ancestor {} + { + swap(*this, node); + /*std::swap(p_data, node.p_data); + std::swap(p_descendants, node.p_descendants); + std::swap(p_ancestor, node.p_ancestor);*/ + } + + inline + TreeNode& operator=(TreeNode other) + { + swap(*this, other); + return *this; + } + + inline explicit + TreeNode(const value_type& data) : + p_data {new value_type {data}}, + p_descendants {}, + p_ancestor {} + {} + + inline + TreeNode(const value_type& data, const darray& descendants, TreeNode* ancestor) : + p_data {new value_type {data}}, + p_descendants {descendants}, + p_ancestor {ancestor} + { + for (auto descendant : p_descendants) + descendant->ancestor(this); + } + + inline + TreeNode(const value_type& data, const darray& descendants) : + p_data {new value_type {data}}, + p_descendants {descendants}, + p_ancestor {} + { + for (auto descendant : p_descendants) + descendant->ancestor(this); + } + + virtual + ~TreeNode() + { + delete p_data; + //delete p_ancestor; + } + + // Member functions + + inline + pointer data() + { + return p_data; + } + + inline + void ancestor(TreeNode* node) + { + p_ancestor = node; + } + + inline + TreeNode* ancestor() + { + return p_ancestor; + } + + inline + void descendants(const darray& descendants) + { + p_descendants = descendants; + } + + inline + darray& descendants() + { + return p_descendants; + } + + darray traverse_descendants() + { + std::function(TreeNode*)> collect = [&](TreeNode* node) + { + darray ds; + + if (!node->descendants().is_empty()) + { + for (TreeNode* dnode: node->descendants()) + { + ds.push(dnode); + if (!dnode->descendants().is_empty()) + ds.push(collect(dnode)); + } + } + + return ds; + }; + + return collect(this); + } + + darray traverse_ancestors() + { + std::function(TreeNode*)> collect = [&](TreeNode* node) + { + darray ds; + + if (node->p_ancestor) + { + ds.push(node); + ds.push(collect(node->p_ancestor)); + } + + return ds; + }; + + return collect(this); + } +}; + +} \ No newline at end of file diff --git a/source/hpr/containers/tests/CMakeLists.txt b/source/hpr/containers/tests/CMakeLists.txt deleted file mode 100644 index 731b2ca..0000000 --- a/source/hpr/containers/tests/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ - -file(GLOB tests_cpp "*.cpp") - -add_executable(${PROJECT_NAME}-tests - ${tests_cpp} -) - -target_link_libraries(${PROJECT_NAME}-tests - PUBLIC - hpr::containers - PRIVATE - GTest::gtest_main -) - -gtest_add_tests(TARGET ${PROJECT_NAME}-tests) \ No newline at end of file diff --git a/source/hpr/containers/tests/containers-test.cpp b/source/hpr/containers/tests/containers-test.cpp index 837f23c..9789623 100644 --- a/source/hpr/containers/tests/containers-test.cpp +++ b/source/hpr/containers/tests/containers-test.cpp @@ -1,37 +1,93 @@ -#include - -#include "../array.hpp" - - -TEST(containers, StaticArray) -{ -hpr::StaticArray arr {1, 3, 2}; -hpr::StaticArray sarr {arr, 5}; -hpr::StaticArray sarr2 {1, 3, 2, 5}; -EXPECT_EQ(sarr, sarr2); -} - -TEST(containers, DynamicArray) -{ -hpr::DynamicArray arr {1, 3, 2}; -hpr::DynamicArray arr2 {1, 3, 2}; -EXPECT_EQ(arr, arr2); -arr.remove(1); -EXPECT_EQ(arr, hpr::darray({1, 2})); -auto iter = arr2.begin(); -++iter; -arr2.remove(iter); -EXPECT_EQ(arr2, hpr::darray({1, 2})); - -hpr::DynamicArray arr3 {1, 3, 0, 2, 9, 0, 5}; -arr3.remove([](float num) { return num == 0; }); -EXPECT_EQ(arr3, hpr::darray({1, 3, 2, 9, 5})); -EXPECT_EQ(arr3.size(), 5); - -hpr::DynamicArray arr4; -arr4.push(new float(5)); -arr4.push(new float(7)); -arr4.push(new float(9)); -EXPECT_EQ(*arr4[0], 5.f); -EXPECT_EQ(*arr4[2], 9.f); -} +#include + +#include +#include + + +TEST(containers, StaticArray) +{ + hpr::StaticArray arr {7, 3, 2}; + hpr::StaticArray sarr {arr, 5}; + hpr::StaticArray sarr2 {7, 3, 2, 5}; + //hpr::StaticArray sarr4; + + EXPECT_EQ(sarr, sarr2); + //EXPECT_EQ(sarr4.data(), nullptr); + //EXPECT_EQ(sarr4.is_empty(), true); + EXPECT_EQ(sarr.size(), 4); + EXPECT_EQ(sarr2.size(), 4); + EXPECT_EQ(sarr[0], 7); + EXPECT_EQ(sarr[1], 3); + EXPECT_EQ(sarr[2], 2); + EXPECT_EQ(sarr[3], 5); +} + +TEST(containers, DynamicArray) +{ + hpr::DynamicArray arr {1, 3, 2}; + hpr::DynamicArray arr2 {1, 3, 2}; + EXPECT_EQ(arr, arr2); + EXPECT_TRUE(arr == arr2); + arr.remove(1); + EXPECT_EQ(arr, hpr::darray({1, 2})); + auto iter = arr2.begin(); + ++iter; + arr2.remove(iter); + EXPECT_EQ(arr2, hpr::darray({1, 2})); + + hpr::DynamicArray arr3 {1, 3, 0, 2, 9, 0, 5}; + arr3.remove([](float num) { return num == 0; }); + EXPECT_EQ(arr3, hpr::darray({1, 3, 2, 9, 5})); + EXPECT_EQ(arr3.size(), 5); + arr3.insert(3, 19); + EXPECT_EQ(arr3.size(), 6); + EXPECT_EQ(arr3[3], 19); + EXPECT_EQ(arr3.pop(), 5); + EXPECT_EQ(arr3.size(), 5); + arr3.resize(3); + EXPECT_EQ(arr3.size(), 3); + EXPECT_EQ(arr3.capacity(), 3); + EXPECT_EQ(arr3.back(), 2); + arr3.resize(4); + EXPECT_EQ(arr3.back(), 2); + arr3.push(17); + arr3.push(14); + EXPECT_EQ(arr3.back(), 14); + EXPECT_EQ(arr3.size(), 5); + + arr3.clear(); + EXPECT_EQ(arr3.is_empty(), true); + + + + hpr::DynamicArray arr4; + arr4.push(new float(5)); + arr4.push(new float(7)); + arr4.push(new float(9)); + EXPECT_EQ(*arr4[0], 5.f); + EXPECT_EQ(*arr4[2], 9.f); + for (auto& v : arr4) + delete v; + + +} + +TEST(containers, TreeNode) +{ + hpr::TreeNode node1 (5); + //std::shared_ptr> ptr {node1}; + hpr::TreeNode node2 {7, {&node1}}; + hpr::TreeNode node3 {9, {&node2, &node1}}; + hpr::TreeNode node4 {11, {&node3}}; + + EXPECT_EQ(*node1.data(), 5); + //EXPECT_EQ(*node1->data(), *node2.ancestor()->data()); + + hpr::darray*> tr = node1.traverse_descendants(); + EXPECT_EQ(tr.size(), 0); + hpr::darray*> tr2 = node4.traverse_descendants(); + EXPECT_EQ(tr2.size(), 4); + hpr::darray*> tr3 = node1.traverse_ancestors(); + EXPECT_EQ(tr3.size(), 2); // node1 has changed ancestor + // +} diff --git a/source/hpr/core.hpp b/source/hpr/core.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/source/hpr/core/CMakeLists.txt b/source/hpr/core/CMakeLists.txt deleted file mode 100644 index 6335592..0000000 --- a/source/hpr/core/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(core - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX -) - -add_library(${PROJECT_NAME} INTERFACE) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS "../core.hpp" "*.hpp") - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ -) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) diff --git a/source/hpr/core/common.hpp b/source/hpr/core/common.hpp deleted file mode 100644 index bdc652a..0000000 --- a/source/hpr/core/common.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define HPR_VERSION_MAJOR 0 -#define HPR_VERSION_MINOR 10 -#define HPR_VERSION_PATCH 0 \ No newline at end of file diff --git a/source/hpr/csg.hpp b/source/hpr/csg.hpp index 089f115..4677564 100644 --- a/source/hpr/csg.hpp +++ b/source/hpr/csg.hpp @@ -1,12 +1,12 @@ -#pragma once - -#include "csg/shape.hpp" -#include "csg/vertex.hpp" -#include "csg/edge.hpp" -#include "csg/wire.hpp" -#include "csg/face.hpp" -#include "csg/shell.hpp" -#include "csg/solid.hpp" -#include "csg/compound.hpp" -#include "csg/geometry.hpp" -#include "csg/surface.hpp" \ No newline at end of file +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/source/hpr/csg/CMakeLists.txt b/source/hpr/csg/CMakeLists.txt index 2b0c56c..cb71843 100644 --- a/source/hpr/csg/CMakeLists.txt +++ b/source/hpr/csg/CMakeLists.txt @@ -1,77 +1,33 @@ -cmake_minimum_required(VERSION 3.16) - -project(csg - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX -) - -add_library(${PROJECT_NAME}) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../csg.hpp" "*.hpp" -) - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES - "*.cpp" -) - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ - PRIVATE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES} -) - -target_link_libraries(${PROJECT_NAME} - ${OCCT_LIBRARIES} -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - ${OCCT_INCLUDE_DIRS} -) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) - - -if(HPR_TEST) - add_subdirectory(tests) -endif() +cmake_minimum_required(VERSION 3.16) + +project(csg + VERSION "${HPR_VERSION}" + LANGUAGES CXX +) + +hpr_add_library(${PROJECT_NAME} STATIC) + +hpr_collect_interface(${PROJECT_NAME} + "../csg.hpp" + "*.hpp" +) + +hpr_collect_sources(${PROJECT_NAME} + "*.cpp" +) + +target_sources(${PROJECT_NAME} + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> + PRIVATE ${${PROJECT_NAME}_SOURCES} +) + +target_link_libraries(${PROJECT_NAME} + ${OpenCASCADE_LIBS} +) + +target_include_directories(${PROJECT_NAME} + PUBLIC ${OpenCASCADE_INCLUDE_DIR} +) + +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) +hpr_tests(${PROJECT_NAME} tests) diff --git a/source/hpr/csg/compound.hpp b/source/hpr/csg/compound.hpp index 4e083eb..79db61c 100644 --- a/source/hpr/csg/compound.hpp +++ b/source/hpr/csg/compound.hpp @@ -1,45 +1,45 @@ -#pragma once - -#include "shape.hpp" -#include "solid.hpp" - - -namespace hpr::csg -{ - - class Compound : public Shape - { - - public: - - Compound() = default; - - ~Compound() override = default; - - explicit - Compound(const Shape& shape) : - Shape {shape.type() == Type::Compound ? shape : throw std::runtime_error("")} - {} - - explicit - Compound(const darray& shapes) : - Shape {} - { - BRep_Builder builder; - TopoDS_Compound compound; - builder.MakeCompound(compound); - - for (auto& shape : shapes) - builder.Add(compound, shape.tshape()); - - p_shape = compound; - } - - [[nodiscard]] - TopoDS_Compound tcast() const - { - return TopoDS::Compound(p_shape); - } - }; - -} +#pragma once + +#include +#include + + +namespace hpr::csg +{ + + class Compound : public Shape + { + + public: + + Compound() = default; + + ~Compound() override = default; + + explicit + Compound(const Shape& shape) : + Shape {shape.type() == Type::Compound ? shape : throw std::runtime_error("")} + {} + + explicit + Compound(const darray& shapes) : + Shape {} + { + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + for (auto& shape : shapes) + builder.Add(compound, shape.tshape()); + + p_shape = compound; + } + + [[nodiscard]] + TopoDS_Compound tcast() const + { + return TopoDS::Compound(p_shape); + } + }; + +} diff --git a/source/hpr/csg/edge.hpp b/source/hpr/csg/edge.hpp index c04f316..0e419ff 100644 --- a/source/hpr/csg/edge.hpp +++ b/source/hpr/csg/edge.hpp @@ -1,39 +1,39 @@ -#pragma once - -#include "shape.hpp" -#include "vertex.hpp" - - -namespace hpr::csg -{ - -class Edge : public Shape -{ - -public: - - Edge() = default; - - ~Edge() override = default; - - explicit - Edge(const Shape& shape) : - Shape {shape.type() == Type::Edge ? shape : throw std::runtime_error("")} - {} - - Edge(const vec3& p1, const vec3& p2) : - Shape {BRepBuilderAPI_MakeEdge(gp_Pnt(p1[0], p1[1], p1[2]), gp_Pnt(p2[0], p2[1], p2[2])).Shape()} - {} - - Edge(const Vertex& v1, const Vertex& v2) : - Shape {BRepBuilderAPI_MakeEdge(v1.tcast(), v2.tcast()).Shape()} - {} - - [[nodiscard]] - TopoDS_Edge tcast() const - { - return TopoDS::Edge(p_shape); - } -}; - -} +#pragma once + +#include +#include + + +namespace hpr::csg +{ + +class Edge : public Shape +{ + +public: + + Edge() = default; + + ~Edge() override = default; + + explicit + Edge(const Shape& shape) : + Shape {shape.type() == Type::Edge ? shape : throw std::runtime_error("")} + {} + + Edge(const vec3& p1, const vec3& p2) : + Shape {BRepBuilderAPI_MakeEdge(gp_Pnt(p1[0], p1[1], p1[2]), gp_Pnt(p2[0], p2[1], p2[2])).Shape()} + {} + + Edge(const Vertex& v1, const Vertex& v2) : + Shape {BRepBuilderAPI_MakeEdge(v1.tcast(), v2.tcast()).Shape()} + {} + + [[nodiscard]] + TopoDS_Edge tcast() const + { + return TopoDS::Edge(p_shape); + } +}; + +} diff --git a/source/hpr/csg/face.hpp b/source/hpr/csg/face.hpp index d68199d..241a52c 100644 --- a/source/hpr/csg/face.hpp +++ b/source/hpr/csg/face.hpp @@ -1,42 +1,42 @@ -#pragma once - -#include "shape.hpp" -#include "wire.hpp" - - -namespace hpr::csg -{ - -class Face : public Shape -{ - -public: - - Face() = default; - - ~Face() override = default; - - explicit - Face(const Shape& shape) : - Shape {shape.type() == Type::Face ? shape : throw std::runtime_error("")} - {} - - explicit - Face(const Wire& wire) : - Shape {BRepBuilderAPI_MakeFace(wire.tcast()).Shape()} - {} - - explicit - Face(const darray& edges) : - Face {Wire(edges)} - {} - - [[nodiscard]] - TopoDS_Face tcast() const - { - return TopoDS::Face(p_shape); - } -}; - -} - +#pragma once + +#include +#include + + +namespace hpr::csg +{ + +class Face : public Shape +{ + +public: + + Face() = default; + + ~Face() override = default; + + explicit + Face(const Shape& shape) : + Shape {shape.type() == Type::Face ? shape : throw std::runtime_error("")} + {} + + explicit + Face(const Wire& wire) : + Shape {BRepBuilderAPI_MakeFace(wire.tcast()).Shape()} + {} + + explicit + Face(const darray& edges) : + Face {Wire(edges)} + {} + + [[nodiscard]] + TopoDS_Face tcast() const + { + return TopoDS::Face(p_shape); + } +}; + +} + diff --git a/source/hpr/csg/geometry.hpp b/source/hpr/csg/geometry.hpp index 4d5f0ca..ceebdbe 100644 --- a/source/hpr/csg/geometry.hpp +++ b/source/hpr/csg/geometry.hpp @@ -1,21 +1,21 @@ -#pragma once - -#include "shape.hpp" - - -namespace hpr::csg -{ - -class Geometry -{ - -public: - - Geometry() = default; - - virtual - ~Geometry() = default; - -}; - +#pragma once + +#include + + +namespace hpr::csg +{ + +class Geometry +{ + +public: + + Geometry() = default; + + virtual + ~Geometry() = default; + +}; + } \ No newline at end of file diff --git a/source/hpr/csg/shape.cpp b/source/hpr/csg/shape.cpp index 5f06224..8d49637 100644 --- a/source/hpr/csg/shape.cpp +++ b/source/hpr/csg/shape.cpp @@ -1,27 +1,27 @@ -#include "shape.hpp" - - -bool std::less::operator()(const hpr::csg::Shape& s1, const hpr::csg::Shape& s2) const -{ - return s1.tshape().HashCode(std::numeric_limits::max()) < - s2.tshape().HashCode(std::numeric_limits::max()); -} -namespace hpr::csg -{ - -std::map Shape::metadata; - - -Shape sphere(vec3 center, double radius) -{ - BRepPrimAPI_MakeSphere prim {gp_Pnt(center[0], center[1], center[2]), radius}; - return Shape {prim.Shape()}; -} - -Shape box(vec3 corner, double dx, double dy, double dz) -{ - BRepPrimAPI_MakeBox prim {gp_Pnt(corner[0], corner[1], corner[2]), dx, dy, dz}; - return Shape {prim.Shape()}; -} - -} +#include + + +bool std::less::operator()(const hpr::csg::Shape& s1, const hpr::csg::Shape& s2) const +{ + return s1.tshape().HashCode(std::numeric_limits::max()) < + s2.tshape().HashCode(std::numeric_limits::max()); +} +namespace hpr::csg +{ + +std::map Shape::metadata; + + +Shape sphere(vec3 center, double radius) +{ + BRepPrimAPI_MakeSphere prim {gp_Pnt(center[0], center[1], center[2]), radius}; + return Shape {prim.Shape()}; +} + +Shape box(vec3 corner, double dx, double dy, double dz) +{ + BRepPrimAPI_MakeBox prim {gp_Pnt(corner[0], corner[1], corner[2]), dx, dy, dz}; + return Shape {prim.Shape()}; +} + +} diff --git a/source/hpr/csg/shape.hpp b/source/hpr/csg/shape.hpp index e8dadb0..fc72cc7 100644 --- a/source/hpr/csg/shape.hpp +++ b/source/hpr/csg/shape.hpp @@ -1,490 +1,490 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar.hpp" -#include "../math/vector.hpp" - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - - -namespace hpr::csg -{ - class Shape; -} - -namespace std -{ - template<> - struct less - { - bool operator() (const hpr::csg::Shape& s1, const hpr::csg::Shape& s2) const; - }; -} - -namespace hpr::csg -{ - -// Forward declaration of friend functions - -double distance(const Shape& lhs, const Shape& rhs); - -Shape fuse(const Shape& lhs, const Shape& rhs); - -Shape fuse(const darray& args, const darray& tools); - -Shape common(const Shape& lhs, const Shape& rhs); - -Shape cut(const Shape& lhs, const Shape& rhs); - - -// Class declaration - -class Shape -{ - -public: - - enum class Type - { - Compound, - Compsolid, - Solid, - Shell, - Face, - Wire, - Edge, - Vertex, - Shape, - Unknown - }; - - enum class Format - { - Unknown, - STEP - }; - - class Metadata - { - public: - std::string label; - public: - Metadata() : - label {"default"} - {} - void merge(const Metadata& data) - { - if (label == "default" && data.label != "default") - label = data.label; - } - }; - -public: - - // TODO: clean up map - static - std::map metadata; - -protected: - - TopoDS_Shape p_shape; - -public: - - [[nodiscard]] - TopoDS_Shape tshape() const { return p_shape; } - - Shape(const TopoDS_Shape& s) : - p_shape {s} - {} - - Shape(TopoDS_Shape&& s) noexcept: - p_shape {std::forward(s)} - {} - -public: - - Shape() : - p_shape {} - {} - - virtual - ~Shape() = default; - //{ - //if (metadata.contains(*this)) - // metadata.erase(*this); - //} - - [[nodiscard]] - Type type() const - { - switch (p_shape.ShapeType()) - { - case TopAbs_VERTEX: - return Type::Vertex; - case TopAbs_EDGE: - return Type::Edge; - case TopAbs_FACE: - return Type::Face; - case TopAbs_WIRE: - return Type::Wire; - case TopAbs_SHELL: - return Type::Shell; - case TopAbs_SOLID: - return Type::Solid; - case TopAbs_COMPOUND: - return Type::Compound; - case TopAbs_COMPSOLID: - return Type::Compsolid; - case TopAbs_SHAPE: - return Type::Shape; - default: - return Type::Unknown; - } - } - - [[nodiscard]] - vec3 center() const - { - GProp_GProps props; - - switch (type()) - { - case Type::Solid: - case Type::Compsolid: - case Type::Compound: - BRepGProp::VolumeProperties(p_shape, props); - case Type::Shell: - case Type::Face: - BRepGProp::SurfaceProperties(p_shape, props); - default: - BRepGProp::LinearProperties(p_shape, props); - } - - gp_Pnt center {props.CentreOfMass()}; - - return vec3 {center.X(), center.Y(), center.Z()}; - } - - [[nodiscard]] - double length() const - { - GProp_GProps props; - - switch (type()) - { - case Type::Vertex: - return 0; - default: - BRepGProp::LinearProperties(p_shape, props); - return props.Mass(); - } - } - - [[nodiscard]] - double area() const - { - GProp_GProps props; - - switch (type()) - { - case Type::Vertex: - case Type::Edge: - case Type::Wire: - return 0; - default: - BRepGProp::SurfaceProperties(p_shape, props); - return props.Mass(); - } - } - - [[nodiscard]] - double volume() const - { - GProp_GProps props; - - switch (type()) - { - case Type::Compsolid: - case Type::Solid: - BRepGProp::VolumeProperties(p_shape, props); - return props.Mass(); - - default: - return 0; - } - } - - void label(const std::string& label) const - { - metadata[*this].label = label; - } - - [[nodiscard]] - std::string label() const - { - return metadata[*this].label; - } - - void dump(const std::string& filename, Format format) const - { - if (p_shape.IsNull()) - throw std::runtime_error("Trying to export null shape"); - - switch (format) - { - case Format::STEP: - { - STEPControl_Writer writer; - Interface_Static::SetCVal("xstep.cascade.unit", "MM"); - Interface_Static::SetCVal("write.step.unit", "MM"); - Interface_Static::SetIVal("write.step.nonmanifold", 1); - - writer.Transfer(p_shape, STEPControl_AsIs); - writer.Write(filename.c_str()); - break; - } - case Format::Unknown: - default: - throw std::invalid_argument("Unknown export format"); - } - } - - // - - [[nodiscard]] - sarray boundingBox() const - { - Bnd_Box bbox; - BRepBndLib::Add(p_shape, bbox, true); - gp_Pnt p1 {bbox.CornerMin()}; - gp_Pnt p2 {bbox.CornerMax()}; - - return sarray {{p1.X(), p1.Y(), p1.Z()}, {p2.X(), p2.Y(), p2.Z()}}; - } - - void incrementalMesh(double deflection) - { - BRepTools::Clean(p_shape); - BRepMesh_IncrementalMesh(p_shape, deflection, true); - } - - darray subShapes(Type type) const - { - darray subshapes; - for (TopExp_Explorer exp(p_shape, static_cast(type)); exp.More(); exp.Next()) - subshapes.push(Shape(exp.Current())); - return subshapes; - } - - darray edges() const - { - return subShapes(Type::Edge); - } - - darray faces() const - { - return subShapes(Type::Face); - } - - darray shells() const - { - return subShapes(Type::Shell); - } - - // Member functions: transformations - - Shape translate(const vec3& dir) - { - gp_Trsf transform; - transform.SetTranslation(gp_Vec(dir[0], dir[1], dir[2])); - BRepBuilderAPI_Transform builder {p_shape, transform, true}; - - return builder.Shape(); - } - - Shape rotate(const vec3& pos, const vec3& axis, double angle) - { - gp_Trsf transform; - transform.SetRotation(gp_Ax1({pos[0], pos[1], pos[2]}, {axis[0], axis[1], axis[2]}), rad(angle)); - BRepBuilderAPI_Transform builder {p_shape, transform, true}; - - return builder.Shape(); - } - - Shape scale(const vec3& center, double scale) - { - gp_Trsf transform; - transform.SetScale(gp_Pnt(center[0], center[1], center[2]), scale); - BRepBuilderAPI_Transform builder {p_shape, transform, true}; - - return builder.Shape(); - } - - Shape& scaled(const vec3& center, double scale) - { - p_shape = this->scale(center, scale).p_shape; - return *this; - } - - Shape extrude(const vec3& dir, double length) - { - BRepPrimAPI_MakePrism builder {p_shape, length * gp_Vec(dir[0], dir[1], dir[2]), true}; - - for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) - for (TopExp_Explorer exp {p_shape, static_cast(type)}; exp.More(); exp.Next()) - { - auto data = metadata[Shape(exp.Current())]; - - for (auto& mod : builder.Generated(exp.Current())) - metadata[Shape(mod)].merge(data); - } - - return builder.Shape(); - } - - Shape fillet(darray edges, double radius) - { - BRepFilletAPI_MakeFillet fillet {p_shape}; - - for (auto& e : edges) - fillet.Add(radius, TopoDS::Edge(e.p_shape)); - fillet.Build(); - - return fillet.Shape(); - } - - // Friend functions - - friend - double distance(const Shape& lhs, const Shape& rhs) - { - return BRepExtrema_DistShapeShape(lhs.tshape(), rhs.tshape()).Value(); - } - - friend - Shape fuse(const Shape& lhs, const Shape& rhs) - { - BRepAlgoAPI_Fuse builder {lhs.p_shape, rhs.p_shape}; - builder.Build(); - return Shape {builder.Shape()}; - } - - friend - Shape fuse(const darray& args, const darray& tools) - { - BRepAlgoAPI_Fuse builder; - NCollection_List args_, tools_; - for (auto& arg : args) - args_.Append(arg.tshape()); - for (auto& tool : tools) - tools_.Append(tool.tshape()); - builder.SetArguments(args_); - builder.SetTools(tools_); - builder.Build(); - return Shape {builder.Shape()}; - } - - friend - Shape common(const Shape& lhs, const Shape& rhs) - { - BRepAlgoAPI_Common builder {lhs.p_shape, rhs.p_shape}; - builder.Build(); - return Shape {builder.Shape()}; - } - - friend - Shape cut(const Shape& lhs, const Shape& rhs) - { - BRepAlgoAPI_Cut builder {lhs.p_shape, rhs.p_shape}; - builder.Build(); - for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) - for (TopExp_Explorer exp {lhs.p_shape, static_cast(type)}; exp.More(); exp.Next()) - { - auto data = metadata[Shape(exp.Current())]; - - for (auto& mod : builder.Modified(exp.Current())) - metadata[Shape(mod)].merge(data); - } - for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) - for (TopExp_Explorer exp {rhs.p_shape, static_cast(type)}; exp.More(); exp.Next()) - { - auto data = metadata[Shape(exp.Current())]; - - for (auto& mod : builder.Modified(exp.Current())) - metadata[Shape(mod)].merge(data); - } - return Shape {builder.Shape()}; - } - -}; - -// Global functions: primitives - -Shape sphere(vec3 center, double radius); - -Shape box(vec3 corner, double dx, double dy, double dz); - -} - - - - +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + + +namespace hpr::csg +{ + class Shape; +} + +namespace std +{ + template<> + struct less + { + bool operator() (const hpr::csg::Shape& s1, const hpr::csg::Shape& s2) const; + }; +} + +namespace hpr::csg +{ + +// Forward declaration of friend functions + +double distance(const Shape& lhs, const Shape& rhs); + +Shape fuse(const Shape& lhs, const Shape& rhs); + +Shape fuse(const darray& args, const darray& tools); + +Shape common(const Shape& lhs, const Shape& rhs); + +Shape cut(const Shape& lhs, const Shape& rhs); + + +// Class declaration + +class Shape +{ + +public: + + enum class Type + { + Compound, + Compsolid, + Solid, + Shell, + Face, + Wire, + Edge, + Vertex, + Shape, + Unknown + }; + + enum class Format + { + Unknown, + STEP + }; + + class Metadata + { + public: + std::string label; + public: + Metadata() : + label {"default"} + {} + void merge(const Metadata& data) + { + if (label == "default" && data.label != "default") + label = data.label; + } + }; + +public: + + // TODO: clean up map + static + std::map metadata; + +protected: + + TopoDS_Shape p_shape; + +public: + + [[nodiscard]] + TopoDS_Shape tshape() const { return p_shape; } + + Shape(const TopoDS_Shape& s) : + p_shape {s} + {} + + Shape(TopoDS_Shape&& s) noexcept: + p_shape {std::forward(s)} + {} + +public: + + Shape() : + p_shape {} + {} + + virtual + ~Shape() = default; + //{ + //if (metadata.contains(*this)) + // metadata.erase(*this); + //} + + [[nodiscard]] + Type type() const + { + switch (p_shape.ShapeType()) + { + case TopAbs_VERTEX: + return Type::Vertex; + case TopAbs_EDGE: + return Type::Edge; + case TopAbs_FACE: + return Type::Face; + case TopAbs_WIRE: + return Type::Wire; + case TopAbs_SHELL: + return Type::Shell; + case TopAbs_SOLID: + return Type::Solid; + case TopAbs_COMPOUND: + return Type::Compound; + case TopAbs_COMPSOLID: + return Type::Compsolid; + case TopAbs_SHAPE: + return Type::Shape; + default: + return Type::Unknown; + } + } + + [[nodiscard]] + vec3 center() const + { + GProp_GProps props; + + switch (type()) + { + case Type::Solid: + case Type::Compsolid: + case Type::Compound: + BRepGProp::VolumeProperties(p_shape, props); + case Type::Shell: + case Type::Face: + BRepGProp::SurfaceProperties(p_shape, props); + default: + BRepGProp::LinearProperties(p_shape, props); + } + + gp_Pnt center {props.CentreOfMass()}; + + return vec3 {center.X(), center.Y(), center.Z()}; + } + + [[nodiscard]] + double length() const + { + GProp_GProps props; + + switch (type()) + { + case Type::Vertex: + return 0; + default: + BRepGProp::LinearProperties(p_shape, props); + return props.Mass(); + } + } + + [[nodiscard]] + double area() const + { + GProp_GProps props; + + switch (type()) + { + case Type::Vertex: + case Type::Edge: + case Type::Wire: + return 0; + default: + BRepGProp::SurfaceProperties(p_shape, props); + return props.Mass(); + } + } + + [[nodiscard]] + double volume() const + { + GProp_GProps props; + + switch (type()) + { + case Type::Compsolid: + case Type::Solid: + BRepGProp::VolumeProperties(p_shape, props); + return props.Mass(); + + default: + return 0; + } + } + + void label(const std::string& label) const + { + metadata[*this].label = label; + } + + [[nodiscard]] + std::string label() const + { + return metadata[*this].label; + } + + void dump(const std::string& filename, Format format) const + { + if (p_shape.IsNull()) + throw std::runtime_error("Trying to export null shape"); + + switch (format) + { + case Format::STEP: + { + STEPControl_Writer writer; + Interface_Static::SetCVal("xstep.cascade.unit", "MM"); + Interface_Static::SetCVal("write.step.unit", "MM"); + Interface_Static::SetIVal("write.step.nonmanifold", 1); + + writer.Transfer(p_shape, STEPControl_AsIs); + writer.Write(filename.c_str()); + break; + } + case Format::Unknown: + default: + throw std::invalid_argument("Unknown export format"); + } + } + + // + + [[nodiscard]] + sarray boundingBox() const + { + Bnd_Box bbox; + BRepBndLib::Add(p_shape, bbox, true); + gp_Pnt p1 {bbox.CornerMin()}; + gp_Pnt p2 {bbox.CornerMax()}; + + return sarray {{p1.X(), p1.Y(), p1.Z()}, {p2.X(), p2.Y(), p2.Z()}}; + } + + void incrementalMesh(double deflection) + { + BRepTools::Clean(p_shape); + BRepMesh_IncrementalMesh(p_shape, deflection, true); + } + + darray subShapes(Type type) const + { + darray subshapes; + for (TopExp_Explorer exp(p_shape, static_cast(type)); exp.More(); exp.Next()) + subshapes.push(Shape(exp.Current())); + return subshapes; + } + + darray edges() const + { + return subShapes(Type::Edge); + } + + darray faces() const + { + return subShapes(Type::Face); + } + + darray shells() const + { + return subShapes(Type::Shell); + } + + // Member functions: transformations + + Shape translate(const vec3& dir) + { + gp_Trsf transform; + transform.SetTranslation(gp_Vec(dir[0], dir[1], dir[2])); + BRepBuilderAPI_Transform builder {p_shape, transform, true}; + + return builder.Shape(); + } + + Shape rotate(const vec3& pos, const vec3& axis, double angle) + { + gp_Trsf transform; + transform.SetRotation(gp_Ax1({pos[0], pos[1], pos[2]}, {axis[0], axis[1], axis[2]}), rad(angle)); + BRepBuilderAPI_Transform builder {p_shape, transform, true}; + + return builder.Shape(); + } + + Shape scale(const vec3& center, double scale) + { + gp_Trsf transform; + transform.SetScale(gp_Pnt(center[0], center[1], center[2]), scale); + BRepBuilderAPI_Transform builder {p_shape, transform, true}; + + return builder.Shape(); + } + + Shape& scaled(const vec3& center, double scale) + { + p_shape = this->scale(center, scale).p_shape; + return *this; + } + + Shape extrude(const vec3& dir, double length) + { + BRepPrimAPI_MakePrism builder {p_shape, length * gp_Vec(dir[0], dir[1], dir[2]), true}; + + for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) + for (TopExp_Explorer exp {p_shape, static_cast(type)}; exp.More(); exp.Next()) + { + auto data = metadata[Shape(exp.Current())]; + + for (auto& mod : builder.Generated(exp.Current())) + metadata[Shape(mod)].merge(data); + } + + return builder.Shape(); + } + + Shape fillet(darray edges, double radius) + { + BRepFilletAPI_MakeFillet fillet {p_shape}; + + for (auto& e : edges) + fillet.Add(radius, TopoDS::Edge(e.p_shape)); + fillet.Build(); + + return fillet.Shape(); + } + + // Friend functions + + friend + double distance(const Shape& lhs, const Shape& rhs) + { + return BRepExtrema_DistShapeShape(lhs.tshape(), rhs.tshape()).Value(); + } + + friend + Shape fuse(const Shape& lhs, const Shape& rhs) + { + BRepAlgoAPI_Fuse builder {lhs.p_shape, rhs.p_shape}; + builder.Build(); + return Shape {builder.Shape()}; + } + + friend + Shape fuse(const darray& args, const darray& tools) + { + BRepAlgoAPI_Fuse builder; + NCollection_List args_, tools_; + for (auto& arg : args) + args_.Append(arg.tshape()); + for (auto& tool : tools) + tools_.Append(tool.tshape()); + builder.SetArguments(args_); + builder.SetTools(tools_); + builder.Build(); + return Shape {builder.Shape()}; + } + + friend + Shape common(const Shape& lhs, const Shape& rhs) + { + BRepAlgoAPI_Common builder {lhs.p_shape, rhs.p_shape}; + builder.Build(); + return Shape {builder.Shape()}; + } + + friend + Shape cut(const Shape& lhs, const Shape& rhs) + { + BRepAlgoAPI_Cut builder {lhs.p_shape, rhs.p_shape}; + builder.Build(); + for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) + for (TopExp_Explorer exp {lhs.p_shape, static_cast(type)}; exp.More(); exp.Next()) + { + auto data = metadata[Shape(exp.Current())]; + + for (auto& mod : builder.Modified(exp.Current())) + metadata[Shape(mod)].merge(data); + } + for (auto& type : { Type::Solid, Type::Face, Type::Edge, Type::Vertex }) + for (TopExp_Explorer exp {rhs.p_shape, static_cast(type)}; exp.More(); exp.Next()) + { + auto data = metadata[Shape(exp.Current())]; + + for (auto& mod : builder.Modified(exp.Current())) + metadata[Shape(mod)].merge(data); + } + return Shape {builder.Shape()}; + } + +}; + +// Global functions: primitives + +Shape sphere(vec3 center, double radius); + +Shape box(vec3 corner, double dx, double dy, double dz); + +} + + + + diff --git a/source/hpr/csg/shell.hpp b/source/hpr/csg/shell.hpp index 9044425..7de4424 100644 --- a/source/hpr/csg/shell.hpp +++ b/source/hpr/csg/shell.hpp @@ -1,52 +1,52 @@ -#pragma once - -#include "shape.hpp" -#include "face.hpp" - - -namespace hpr::csg -{ - -class Shell : public Shape -{ - -public: - - Shell() = default; - - ~Shell() override = default; - - explicit - Shell(const Shape& shape) : - Shape {shape.type() == Type::Shell ? shape : throw std::runtime_error("")} - {} - - explicit - Shell(const darray& faces) : - Shape {} - { - BRep_Builder builder; - TopoDS_Shell shell; - builder.MakeShell(shell); - - for (auto& shape : faces) - switch (shape.type()) - { - case Type::Face: - builder.Add(shell, Face(shape).tcast()); - break; - default: - throw std::runtime_error(""); - } - - p_shape = shell; - } - - [[nodiscard]] - TopoDS_Shell tcast() const - { - return TopoDS::Shell(p_shape); - } -}; - -} +#pragma once + +#include +#include + + +namespace hpr::csg +{ + +class Shell : public Shape +{ + +public: + + Shell() = default; + + ~Shell() override = default; + + explicit + Shell(const Shape& shape) : + Shape {shape.type() == Type::Shell ? shape : throw std::runtime_error("")} + {} + + explicit + Shell(const darray& faces) : + Shape {} + { + BRep_Builder builder; + TopoDS_Shell shell; + builder.MakeShell(shell); + + for (auto& shape : faces) + switch (shape.type()) + { + case Type::Face: + builder.Add(shell, Face(shape).tcast()); + break; + default: + throw std::runtime_error(""); + } + + p_shape = shell; + } + + [[nodiscard]] + TopoDS_Shell tcast() const + { + return TopoDS::Shell(p_shape); + } +}; + +} diff --git a/source/hpr/csg/solid.hpp b/source/hpr/csg/solid.hpp index 88320a1..1631b1d 100644 --- a/source/hpr/csg/solid.hpp +++ b/source/hpr/csg/solid.hpp @@ -1,45 +1,45 @@ -#pragma once - -#include "shape.hpp" -#include "shell.hpp" - - -namespace hpr::csg -{ - - class Solid : public Shape - { - - public: - - Solid() = default; - - ~Solid() override = default; - - explicit - Solid(const Shape& shape) : - Shape {shape.type() == Type::Solid ? shape : throw std::runtime_error("")} - {} - - explicit - Solid(const Shell& shell) : - Shape {} - { - BRep_Builder builder; - TopoDS_Solid solid; - - builder.MakeSolid(solid); - builder.Add(solid, shell.tcast()); - - p_shape = solid; - } - - [[nodiscard]] - TopoDS_Solid tcast() const - { - return TopoDS::Solid(p_shape); - } - }; - -} - +#pragma once + +#include +#include + + +namespace hpr::csg +{ + + class Solid : public Shape + { + + public: + + Solid() = default; + + ~Solid() override = default; + + explicit + Solid(const Shape& shape) : + Shape {shape.type() == Type::Solid ? shape : throw std::runtime_error("")} + {} + + explicit + Solid(const Shell& shell) : + Shape {} + { + BRep_Builder builder; + TopoDS_Solid solid; + + builder.MakeSolid(solid); + builder.Add(solid, shell.tcast()); + + p_shape = solid; + } + + [[nodiscard]] + TopoDS_Solid tcast() const + { + return TopoDS::Solid(p_shape); + } + }; + +} + diff --git a/source/hpr/csg/surface.hpp b/source/hpr/csg/surface.hpp index cf95fb5..9868ec1 100644 --- a/source/hpr/csg/surface.hpp +++ b/source/hpr/csg/surface.hpp @@ -1,64 +1,64 @@ -#pragma once - -#include "geometry.hpp" -#include "face.hpp" - - -namespace hpr::csg -{ - - class Surface : public Geometry - { - - protected: - - Handle(Geom_Surface) p_surface; - - public: - - Surface() = default; - - ~Surface() override = default; - - explicit - Surface(const Face& face) : - Geometry {}, - p_surface {BRep_Tool::Surface(face.tcast())} - {} - - [[nodiscard]] - Handle(Geom_Surface) tcast() const - { - return p_surface; - } - - [[nodiscard]] - vec3 value(double u, double v) const - { - gp_Pnt p {p_surface->Value(u, v)}; - - return vec3 {p.X(), p.Y(), p.Z()}; - } - - [[nodiscard]] - vec3 normal(double u, double v) const - { - GeomLProp_SLProps props {p_surface, u, v, 1, 1e-8}; - gp_Dir dir {props.Normal()}; - - return vec3 {dir.X(), dir.Y(), dir.Z()}; - } - - vec3 normal() - { - gp_Vec du, dv; - gp_Pnt p; - p_surface->D1(0, 0, p, du, dv); - gp_Vec dir {du ^ dv}; - return vec3 {dir.X(), dir.Y(), dir.Z()}; - } - }; - -} - - +#pragma once + +#include +#include + + +namespace hpr::csg +{ + + class Surface : public Geometry + { + + protected: + + Handle(Geom_Surface) p_surface; + + public: + + Surface() = default; + + ~Surface() override = default; + + explicit + Surface(const Face& face) : + Geometry {}, + p_surface {BRep_Tool::Surface(face.tcast())} + {} + + [[nodiscard]] + Handle(Geom_Surface) tcast() const + { + return p_surface; + } + + [[nodiscard]] + vec3 value(double u, double v) const + { + gp_Pnt p {p_surface->Value(u, v)}; + + return vec3 {p.X(), p.Y(), p.Z()}; + } + + [[nodiscard]] + vec3 normal(double u, double v) const + { + GeomLProp_SLProps props {p_surface, u, v, 1, 1e-8}; + gp_Dir dir {props.Normal()}; + + return vec3 {dir.X(), dir.Y(), dir.Z()}; + } + + vec3 normal() + { + gp_Vec du, dv; + gp_Pnt p; + p_surface->D1(0, 0, p, du, dv); + gp_Vec dir {du ^ dv}; + return vec3 {dir.X(), dir.Y(), dir.Z()}; + } + }; + +} + + diff --git a/source/hpr/csg/tests/CMakeLists.txt b/source/hpr/csg/tests/CMakeLists.txt deleted file mode 100644 index dcdd184..0000000 --- a/source/hpr/csg/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -file(GLOB tests_cpp "*.cpp") - -add_executable(${PROJECT_NAME}-tests - ${tests_cpp} - ) - -target_link_libraries(${PROJECT_NAME}-tests - PUBLIC - hpr::${PROJECT_NAME} - PRIVATE - GTest::gtest_main - ) - -gtest_add_tests(TARGET ${PROJECT_NAME}-tests) diff --git a/source/hpr/csg/tests/csg-test.cpp b/source/hpr/csg/tests/csg-test.cpp index 73cbc48..4718825 100644 --- a/source/hpr/csg/tests/csg-test.cpp +++ b/source/hpr/csg/tests/csg-test.cpp @@ -1,24 +1,25 @@ -#include -#include "../../csg.hpp" - -TEST(csgTest, Shape) -{ - using namespace hpr; - double radius = 1.; - double volume = 4. / 3. * PI; - auto sphere = csg::sphere({0, 0, 0}, radius); - EXPECT_TRUE(equal(sphere.volume(), volume, 1e-6)); - auto box = csg::box({0, 0, 0}, 1, 1, 1); - EXPECT_TRUE(equal(box.volume(), 1.)); - auto edge = csg::Edge(); - int n = 0; - for (auto& face : box.subShapes(csg::Shape::Type::Face)) - { - std::stringstream name; - name << "face" << n; - csg::Face(face).label(name.str()); - ++n; - } - box.scale(box.center(), 5); - EXPECT_EQ(box.subShapes(csg::Shape::Type::Face)[2].label(), "face2"); +#include +#include + + +TEST(csgTest, Shape) +{ + using namespace hpr; + double radius = 1.; + double volume = 4. / 3. * pi(); + auto sphere = csg::sphere({0, 0, 0}, radius); + EXPECT_TRUE(equal(sphere.volume(), volume, 1e-6)); + auto box = csg::box({0, 0, 0}, 1, 1, 1); + EXPECT_TRUE(equal(box.volume(), 1.)); + auto edge = csg::Edge(); + int n = 0; + for (auto& face : box.subShapes(csg::Shape::Type::Face)) + { + std::stringstream name; + name << "face" << n; + csg::Face(face).label(name.str()); + ++n; + } + box.scale(box.center(), 5); + EXPECT_EQ(box.subShapes(csg::Shape::Type::Face)[2].label(), "face2"); } \ No newline at end of file diff --git a/source/hpr/csg/vertex.hpp b/source/hpr/csg/vertex.hpp index 031a2ba..f0f6591 100644 --- a/source/hpr/csg/vertex.hpp +++ b/source/hpr/csg/vertex.hpp @@ -1,43 +1,43 @@ -#pragma once - -#include "shape.hpp" - - -namespace hpr::csg -{ - -class Vertex : public Shape -{ - -public: - - Vertex() = default; - - ~Vertex() override = default; - - explicit - Vertex(const Shape& shape) : - Shape {shape.type() == Type::Vertex ? shape : throw std::runtime_error("")} - {} - - explicit - Vertex(const vec3& point) : - Shape {BRepBuilderAPI_MakeVertex(gp_Pnt(point[0], point[1], point[2])).Shape()} - {} - - [[nodiscard]] - TopoDS_Vertex tcast() const - { - return TopoDS::Vertex(p_shape); - } - - [[nodiscard]] - vec3 cast() const - { - gp_Pnt point = BRep_Tool::Pnt(tcast()); - return vec3 {point.X(), point.Y(), point.Z()}; - } -}; - -} - +#pragma once + +#include + + +namespace hpr::csg +{ + +class Vertex : public Shape +{ + +public: + + Vertex() = default; + + ~Vertex() override = default; + + explicit + Vertex(const Shape& shape) : + Shape {shape.type() == Type::Vertex ? shape : throw std::runtime_error("")} + {} + + explicit + Vertex(const vec3& point) : + Shape {BRepBuilderAPI_MakeVertex(gp_Pnt(point[0], point[1], point[2])).Shape()} + {} + + [[nodiscard]] + TopoDS_Vertex tcast() const + { + return TopoDS::Vertex(p_shape); + } + + [[nodiscard]] + vec3 cast() const + { + gp_Pnt point = BRep_Tool::Pnt(tcast()); + return vec3 {point.X(), point.Y(), point.Z()}; + } +}; + +} + diff --git a/source/hpr/csg/wire.hpp b/source/hpr/csg/wire.hpp index 104d8ee..cd6e1d3 100644 --- a/source/hpr/csg/wire.hpp +++ b/source/hpr/csg/wire.hpp @@ -1,53 +1,53 @@ -#pragma once - -#include "shape.hpp" -#include "edge.hpp" - - -namespace hpr::csg -{ - -class Wire : public Shape -{ - -public: - - Wire() = default; - - ~Wire() override = default; - - explicit - Wire(const Shape& shape) : - Shape {shape.type() == Type::Wire ? shape : throw std::runtime_error("")} - {} - - explicit - Wire(const darray& edges) : - Shape {} - { - BRepBuilderAPI_MakeWire builder; - - for (auto& shape : edges) - switch (shape.type()) - { - case Type::Edge: - builder.Add(Edge(shape).tcast()); - break; - case Type::Wire: - builder.Add(Wire(shape).tcast()); - break; - default: - throw std::runtime_error(""); - } - - p_shape = builder.Shape(); - } - - [[nodiscard]] - TopoDS_Wire tcast() const - { - return TopoDS::Wire(p_shape); - } -}; - -} +#pragma once + +#include +#include + + +namespace hpr::csg +{ + +class Wire : public Shape +{ + +public: + + Wire() = default; + + ~Wire() override = default; + + explicit + Wire(const Shape& shape) : + Shape {shape.type() == Type::Wire ? shape : throw std::runtime_error("")} + {} + + explicit + Wire(const darray& edges) : + Shape {} + { + BRepBuilderAPI_MakeWire builder; + + for (auto& shape : edges) + switch (shape.type()) + { + case Type::Edge: + builder.Add(Edge(shape).tcast()); + break; + case Type::Wire: + builder.Add(Wire(shape).tcast()); + break; + default: + throw std::runtime_error(""); + } + + p_shape = builder.Shape(); + } + + [[nodiscard]] + TopoDS_Wire tcast() const + { + return TopoDS::Wire(p_shape); + } +}; + +} diff --git a/source/hpr/exception.hpp b/source/hpr/exception.hpp new file mode 100644 index 0000000..25ca37a --- /dev/null +++ b/source/hpr/exception.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace hpr +{ + class Exception : public std::exception + { + protected: + std::string p_message; + public: + inline explicit + Exception(std::source_location location = std::source_location::current()) : + p_message {} + { + std::stringstream _message; + _message << "\t" << p_message + << "\n where:\t\t" << location.file_name() << ":" << location.line() << ":" << location.column() + << "\n function:\t" << location.function_name(); + p_message = _message.str(); + } + inline explicit + Exception(const std::string& message, std::source_location location = std::source_location::current()) : + p_message {message} + { + std::stringstream _message; + _message << "\t" << p_message + << "\n where:\t" << location.file_name() << ":" << location.line() << ":" << location.column() + << "\n function:\t" << location.function_name(); + p_message = _message.str(); + } + [[nodiscard]] const char* what() const noexcept override { + std::vector vv; + return p_message.data(); + } + }; + + struct OutOfRange : public Exception + { + inline explicit OutOfRange(std::source_location location = std::source_location::current()) : Exception {"Out of range", location} {} + inline explicit OutOfRange(const std::string& message, std::source_location location = std::source_location::current()) : Exception {message, location} {} + }; + + struct LengthError : public Exception + { + inline explicit LengthError(std::source_location location = std::source_location::current()) : Exception {"Length error", location} {} + inline explicit LengthError(const std::string& message, std::source_location location = std::source_location::current()) : Exception {message, location} {} + }; +} \ No newline at end of file diff --git a/source/hpr/geometry.hpp b/source/hpr/geometry.hpp index d3e9d51..9dce06f 100644 --- a/source/hpr/geometry.hpp +++ b/source/hpr/geometry.hpp @@ -1,5 +1,5 @@ -#pragma once - -#include "geometry/polytope.hpp" -#include "geometry/triangle.hpp" -#include "geometry/tetrahedron.hpp" +#pragma once + +#include +#include +#include diff --git a/source/hpr/geometry/CMakeLists.txt b/source/hpr/geometry/CMakeLists.txt index 475011b..8ab1f93 100644 --- a/source/hpr/geometry/CMakeLists.txt +++ b/source/hpr/geometry/CMakeLists.txt @@ -1,57 +1,21 @@ cmake_minimum_required(VERSION 3.16) project(geometry - VERSION "${HPR_PROJECT_VERSION}" + VERSION "${HPR_VERSION}" LANGUAGES CXX ) -add_library(${PROJECT_NAME} INTERFACE) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) +hpr_add_library(${PROJECT_NAME} INTERFACE) -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../geometry.hpp" "*.hpp" +hpr_collect_interface(${PROJECT_NAME} + "../geometry.hpp" + "*.hpp" ) -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> ) -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) + +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) + diff --git a/source/hpr/geometry/polytope.hpp b/source/hpr/geometry/polytope.hpp index e614614..1e793c4 100644 --- a/source/hpr/geometry/polytope.hpp +++ b/source/hpr/geometry/polytope.hpp @@ -1,48 +1,48 @@ -#pragma once - -#include "../containers.hpp" -#include "../math.hpp" - - -namespace hpr::geometry -{ - -template -class Polytope -{ - -public: - - enum class Type - { - Nullitope = -1, - Monon, - Dion, - Polygon, - Polyhedron, - Polychoron, - Unknown - }; - -protected: - - const int p_dimension; - const int p_space; - Type p_type; - darray> p_points; - -public: - - Polytope() : - p_dimension {Dim}, - p_space {Space}, - p_type {Type::Unknown}, - p_points {} - {} - - virtual - ~Polytope() = default; - -}; - +#pragma once + +#include +#include + + +namespace hpr::geometry +{ + +template +class Polytope +{ + +public: + + enum class Type + { + Nullitope = -1, + Monon, + Dion, + Polygon, + Polyhedron, + Polychoron, + Unknown + }; + +protected: + + const int p_dimension; + const int p_space; + Type p_type; + darray> p_points; + +public: + + Polytope() : + p_dimension {Dim}, + p_space {Space}, + p_type {Type::Unknown}, + p_points {} + {} + + virtual + ~Polytope() = default; + +}; + } \ No newline at end of file diff --git a/source/hpr/geometry/tetrahedron.hpp b/source/hpr/geometry/tetrahedron.hpp index c082838..ed226ab 100644 --- a/source/hpr/geometry/tetrahedron.hpp +++ b/source/hpr/geometry/tetrahedron.hpp @@ -1,19 +1,21 @@ -#include "../math.hpp" - -namespace hpr::geometry -{ - -vec3 circumCentre(vec3 p1, vec3 p2, vec3 p3, vec3 p4) -{ - vec3 e1 = p2 - p1; - vec3 e2 = p3 - p1; - vec3 e3 = p4 - p1; - mat3 A; - A.row(0, e1); - A.row(1, e2); - A.row(2, e3); - vec3 B = 0.5 * vec3(sum(pow(p2, 2)) - sum(pow(p1, 2)), sum(pow(p3, 2)) - sum(pow(p1, 2)), sum(pow(p4, 2)) - sum(pow(p1, 2))); - return A.inv() * B; -} - +#pragma once + +#include + +namespace hpr::geometry +{ + +vec3 circumCentre(vec3 p1, vec3 p2, vec3 p3, vec3 p4) +{ + vec3 e1 = p2 - p1; + vec3 e2 = p3 - p1; + vec3 e3 = p4 - p1; + mat3 A; + A.row(0, e1); + A.row(1, e2); + A.row(2, e3); + vec3 B = 0.5 * vec3(sum(pow(p2, 2)) - sum(pow(p1, 2)), sum(pow(p3, 2)) - sum(pow(p1, 2)), sum(pow(p4, 2)) - sum(pow(p1, 2))); + return A.inv() * B; +} + } \ No newline at end of file diff --git a/source/hpr/geometry/triangle.hpp b/source/hpr/geometry/triangle.hpp index e138c25..fb6a794 100644 --- a/source/hpr/geometry/triangle.hpp +++ b/source/hpr/geometry/triangle.hpp @@ -1,15 +1,17 @@ -#include "../math.hpp" - -namespace hpr::geometry -{ - -vec2 circumCentre(vec2 p1, vec2 p2, vec2 p3) -{ - vec2 pb1 {(p1 + p2) * 0.5}; - vec2 pb2 {(p2 + p3) * 0.5}; - scalar s1 = (p2[1] - p1[1]) / (p2[0] - p1[0]); - scalar s2 = (p3[1] - p2[1]) / (p3[0] - p2[0]); - return vec2(pb1[0] + s1 * pb1[1], pb2[0] + s2 * pb2[1]) * mat2(1, s1, 1, s2).inv(); -} - +#pragma once + +#include + +namespace hpr::geometry +{ + +vec2 circumCentre(vec2 p1, vec2 p2, vec2 p3) +{ + vec2 pb1 {(p1 + p2) * 0.5}; + vec2 pb2 {(p2 + p3) * 0.5}; + scalar s1 = (p2[1] - p1[1]) / (p2[0] - p1[0]); + scalar s2 = (p3[1] - p2[1]) / (p3[0] - p2[0]); + return vec2(pb1[0] + s1 * pb1[1], pb2[0] + s2 * pb2[1]) * mat2(1, s1, 1, s2).inv(); +} + } \ No newline at end of file diff --git a/source/hpr/gpu.hpp b/source/hpr/gpu.hpp index 08ec17a..cd6a92f 100644 --- a/source/hpr/gpu.hpp +++ b/source/hpr/gpu.hpp @@ -1,15 +1,18 @@ -#pragma once - - -#include "gpu/array_object.hpp" -#include "gpu/buffer_object.hpp" -#include "gpu/color_buffer.hpp" -#include "gpu/cull_face.hpp" -#include "gpu/depth_buffer.hpp" -#include "gpu/framebuffer.hpp" -#include "gpu/renderbuffer.hpp" -#include "gpu/shader.hpp" -#include "gpu/shader_program.hpp" -#include "gpu/stencil_buffer.hpp" -#include "gpu/texture.hpp" -#include "gpu/viewport.hpp" +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/source/hpr/gpu/CMakeLists.txt b/source/hpr/gpu/CMakeLists.txt index 0a76d9e..a4a7d32 100644 --- a/source/hpr/gpu/CMakeLists.txt +++ b/source/hpr/gpu/CMakeLists.txt @@ -1,68 +1,32 @@ -cmake_minimum_required(VERSION 3.16) - -project(gpu - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX -) - -add_library(${PROJECT_NAME}) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../gpu.hpp" "*.hpp" -) - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES - "*.cpp" -) - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ - PRIVATE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES} -) - -target_link_libraries(${PROJECT_NAME} - glad - stb -) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) +cmake_minimum_required(VERSION 3.16) + +project(gpu + VERSION "${HPR_VERSION}" + LANGUAGES CXX +) + +hpr_add_library(${PROJECT_NAME} INTERFACE) + +hpr_collect_interface(${PROJECT_NAME} + "../gpu.hpp" + "*.hpp" +) + +#hpr_collect_sources(${PROJECT_NAME} +# "*.cpp" +#) + +target_sources(${PROJECT_NAME} + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> + PRIVATE ${${PROJECT_NAME}_SOURCES} +) + +target_link_libraries(${PROJECT_NAME} + INTERFACE + glad::glad + glfw::glfw + stb::stb +) + + +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) diff --git a/source/hpr/gpu/array_object.cpp b/source/hpr/gpu/array_object.cpp deleted file mode 100644 index 462e940..0000000 --- a/source/hpr/gpu/array_object.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "array_object.hpp" diff --git a/source/hpr/gpu/array_object.hpp b/source/hpr/gpu/array_object.hpp index ec98aff..231db94 100644 --- a/source/hpr/gpu/array_object.hpp +++ b/source/hpr/gpu/array_object.hpp @@ -1,104 +1,125 @@ -#pragma once - -#include "buffer_object.hpp" - -#include - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - - class ArrayObject - { - - - protected: - - unsigned int p_index; - int p_size; - int p_stride; - bool p_binded; - - public: - - inline - ArrayObject() : - p_index {0}, - p_size {0}, - p_stride {0}, - p_binded {false} - {} - - virtual - ~ArrayObject() = default; - - [[nodiscard]] - int size() const - { - return p_size; - } - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - void create() - { - glGenVertexArrays(1, &p_index); - } - - void bind() - { - glBindVertexArray(p_index); - p_binded = true; - } - - void unbind() - { - glBindVertexArray(0); - p_binded = false; - } - - bool binded() const - { - return p_binded; - } - - void destroy() - { - glDeleteVertexArrays(1, &p_index); - } - - void attribPointer(BufferObject& buffer, unsigned int location, unsigned int size) - { - if (buffer.type() == BufferObject::Type::Unknown) - throw std::runtime_error("Unknown buffer type"); - if (!binded()) - throw std::runtime_error("ArrayObject is not binded"); - if (!buffer.valid()) - throw std::runtime_error("BufferObject is invalid"); - - buffer.bind(); - glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, sizeof(float) * buffer.offset(), static_cast(nullptr)); - glEnableVertexAttribArray(location); - buffer.unbind(); - } - - void draw() - { - - } - - inline - bool valid() const - { - return p_index > 0; - } - }; - -} +#pragma once + +#include + +#include + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + + class ArrayObject + { + public: + + enum Mode + { + Points = GL_POINTS, + LineStrip = GL_LINE_STRIP, + LineLoop = GL_LINE_LOOP, + Lines = GL_LINES, + LineStripAdjacency = GL_LINE_STRIP_ADJACENCY, + LinesAdjacency = GL_LINES_ADJACENCY, + TriangleStrip = GL_TRIANGLE_STRIP, + TriangleFan = GL_TRIANGLE_FAN, + Triangles = 0x0004, //GL_TRIANGLES, + TriangleStripAdjacency = GL_TRIANGLE_STRIP_ADJACENCY, + TrianglesAdjacency = GL_TRIANGLES_ADJACENCY, + Patches = GL_PATCHES + }; + + protected: + + GLuint p_index; + int p_size; + int p_stride; + bool p_binded; + + public: + + inline + ArrayObject() : + p_index {0}, + p_size {0}, + p_stride {0}, + p_binded {false} + {} + + virtual + ~ArrayObject() = default; + + [[nodiscard]] + int size() const + { + return p_size; + } + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + void create() + { + glGenVertexArrays(1, &p_index); + } + + void bind() + { + glBindVertexArray(p_index); + p_binded = true; + } + + void unbind() + { + glBindVertexArray(0); + p_binded = false; + } + + bool binded() const + { + return p_binded; + } + + void destroy() + { + glDeleteVertexArrays(1, &p_index); + } + + void attribPointer(BufferObject& buffer, unsigned int location, int size) + { + if (buffer.type() == BufferObject::Type::Unknown) + throw std::runtime_error("Unknown buffer type"); + if (!binded()) + throw std::runtime_error("ArrayObject is not binded"); + if (!buffer.valid()) + throw std::runtime_error("BufferObject is invalid"); + + buffer.bind(); + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, sizeof(float) * buffer.offset(), static_cast(nullptr)); + buffer.unbind(); + } + + void drawElements(Mode mode, int count) const + { + glDrawElements(mode, count, GL_UNSIGNED_INT, nullptr); + } + + void drawArrays(Mode mode, int count) const + { + glDrawArrays(mode, 0, count); + } + + inline + bool valid() const + { + return p_index > 0; + } + }; + +} diff --git a/source/hpr/gpu/buffer_object.cpp b/source/hpr/gpu/buffer_object.cpp deleted file mode 100644 index 1d4a436..0000000 --- a/source/hpr/gpu/buffer_object.cpp +++ /dev/null @@ -1,27 +0,0 @@ - -#include -#include "buffer_object.hpp" - -namespace hpr::gpu -{ - - void BufferObject::bind() - { - glBindBuffer((GLenum)p_type, p_index); - p_binded = true; - } - - void BufferObject::unbind() - { - glBindBuffer((GLenum)p_type, 0); - p_binded = false; - } - - void BufferObject::destroy() - { - if (p_type == Type::Unknown) - std::runtime_error("Unknown buffer type"); - - glDeleteBuffers(1, &p_index); - } -} diff --git a/source/hpr/gpu/buffer_object.hpp b/source/hpr/gpu/buffer_object.hpp index 02001df..89e5bb6 100644 --- a/source/hpr/gpu/buffer_object.hpp +++ b/source/hpr/gpu/buffer_object.hpp @@ -1,134 +1,153 @@ -#pragma once - -#include "../containers.hpp" - -#include -#ifndef __gl_h_ -#include -#endif - - -namespace hpr::gpu -{ - -class BufferObject -{ - -public: - - enum class Type - { - Vertex = 0x8892, //GL_ARRAY_BUFFER, - Index = 0x8893, //GL_ELEMENT_ARRAY_BUFFER, - Uniform = 0x8A11, //GL_UNIFORM_BUFFER, - Unknown = -1 - }; - -protected: - - Type p_type; - unsigned int p_index; - int p_size; - int p_offset; - bool p_binded; - -public: - - inline - BufferObject() : - p_type {Type::Unknown}, - p_index {0}, - p_size {0}, - p_offset {0}, - p_binded {false} - {} - - explicit inline - BufferObject(Type type) : - p_type {type}, - p_index {0}, - p_size {0}, - p_offset {0}, - p_binded {false} - {} - - virtual - ~BufferObject() = default; - - [[nodiscard]] - int size() const - { - return p_size; - } - - [[nodiscard]] - Type type() const - { - return p_type; - } - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - [[nodiscard]] - unsigned int offset() const - { - return p_offset; - } - - void bind() ; - - void unbind(); - - [[nodiscard]] - bool binded() const - { - return p_binded; - } - - template - void create(const darray& data, unsigned int offset = 0) - { - if (p_type == Type::Unknown) - std::runtime_error("Unknown buffer type"); - - unsigned int drawType; - - if (p_type == Type::Uniform) - drawType = GL_DYNAMIC_DRAW; - else - drawType = GL_STATIC_DRAW; - - glGenBuffers(1, &p_index); - bind(); - glBufferData((GLenum)p_type, sizeof(T) * data.size(), data.data(), drawType); - unbind(); - - p_offset = offset; - } - - template - void edit(const darray& data, unsigned int offset = 0) - { - if (p_type == Type::Unknown) - std::runtime_error("Unknown buffer type"); - - bind(); - glBufferSubData(p_type, offset, sizeof(T) * data.size(), data.data()); - unbind(); - } - - void destroy(); - - [[nodiscard]] - inline - bool valid() const - { - return p_index > 0; - } -}; - +#pragma once + +#include + + +#include +#ifndef __gl_h_ +#include +#endif + + +namespace hpr::gpu +{ + +class BufferObject +{ + +public: + + enum Type + { + Vertex = 0x8892, // GL_ARRAY_BUFFER, + Index = 0x8893, // GL_ELEMENT_ARRAY_BUFFER, + Uniform = 0x8A11, // GL_UNIFORM_BUFFER, + Unknown = -1 + }; + +protected: + + Type p_type; + unsigned int p_index; + int p_size; + unsigned int p_offset; + bool p_binded; + +public: + + inline + BufferObject() : + p_type {Type::Unknown}, + p_index {0}, + p_size {0}, + p_offset {0}, + p_binded {false} + {} + + explicit inline + BufferObject(Type type) : + p_type {type}, + p_index {0}, + p_size {0}, + p_offset {0}, + p_binded {false} + {} + + virtual + ~BufferObject() = default; + + [[nodiscard]] + int size() const + { + return p_size; + } + + [[nodiscard]] + Type type() const + { + return p_type; + } + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + [[nodiscard]] + unsigned int offset() const + { + return p_offset; + } + + void bind() + { + glBindBuffer((GLenum)p_type, p_index); + p_binded = true; + } + + void unbind() + { + glBindBuffer((GLenum)p_type, 0); + p_binded = false; + } + + [[nodiscard]] + bool binded() const + { + return p_binded; + } + + template + void create(const darray& data, unsigned int offset = 0) + { + if (p_type == Type::Unknown) + throw std::runtime_error("Unknown buffer type"); + + unsigned int drawType; + + if (p_type == Type::Uniform) + drawType = GL_DYNAMIC_DRAW; + else + drawType = GL_STATIC_DRAW; + + glGenBuffers(1, &p_index); + bind(); + glBufferData(static_cast(p_type), sizeof(T) * data.size(), data.data(), drawType); + unbind(); + + p_offset = offset; + p_size = data.size(); + } + + template + void edit(const darray& data, unsigned int offset = 0) + { + if (p_type == Type::Unknown) + throw std::runtime_error("Unknown buffer type"); + + bind(); + glBufferSubData(p_type, offset, sizeof(T) * data.size(), data.data()); + unbind(); + + p_offset = offset; + p_size = data.size(); + } + + void destroy() + { + if (p_type == Type::Unknown) + throw std::runtime_error("Unknown buffer type"); + + glDeleteBuffers(1, &p_index); + } + + [[nodiscard]] + inline + bool valid() const + { + return p_index > 0; + } +}; + } \ No newline at end of file diff --git a/source/hpr/gpu/camera.hpp b/source/hpr/gpu/camera.hpp index e11d835..8ac8793 100644 --- a/source/hpr/gpu/camera.hpp +++ b/source/hpr/gpu/camera.hpp @@ -1,40 +1,40 @@ -#pragma once - -#include "../math.hpp" - - -namespace hpr::gpu -{ - -class Camera -{ - -protected: - - vec3 p_front; - vec3 p_up; - vec3 p_left; - - scalar p_yaw; - scalar p_pitch; - scalar p_roll; - - vec3 p_position; - vec3 p_target; - - scalar p_distance; - -public: - - Camera() : - p_front {0., 0., -1.}, - p_up {0., 0., 1.}, - p_left {1., 0., 0.} - {} - - virtual - ~Camera() = default; - -}; - +#pragma once + +#include + + +namespace hpr::gpu +{ + +class Camera +{ + +protected: + + vec3 p_front; + vec3 p_up; + vec3 p_left; + + scalar p_yaw; + scalar p_pitch; + scalar p_roll; + + vec3 p_position; + vec3 p_target; + + scalar p_distance; + +public: + + Camera() : + p_front {0., 0., -1.}, + p_up {0., 0., 1.}, + p_left {1., 0., 0.} + {} + + virtual + ~Camera() = default; + +}; + } \ No newline at end of file diff --git a/source/hpr/gpu/color_buffer.hpp b/source/hpr/gpu/color_buffer.hpp index 6201485..f3c83d3 100644 --- a/source/hpr/gpu/color_buffer.hpp +++ b/source/hpr/gpu/color_buffer.hpp @@ -1,68 +1,70 @@ -#pragma once - -#include "../math/vector.hpp" -#ifndef __gl_h_ -#include -#endif - - - -namespace hpr::gpu -{ - - class ColorBuffer - { - - protected: - - bool p_enabledRed; - bool p_enabledGreen; - bool p_enabledBlue; - bool p_enabledAlpha; - vec4 p_color; - - public: - - inline - ColorBuffer() : - p_enabledRed {true}, - p_enabledGreen {true}, - p_enabledBlue {true}, - p_enabledAlpha {true}, - p_color {} - {} - - inline - ColorBuffer(bool red, bool green, bool blue, bool alpha) : - p_enabledRed {red}, - p_enabledGreen {green}, - p_enabledBlue {blue}, - p_enabledAlpha {alpha}, - p_color {} - {} - - virtual - ~ColorBuffer() = default; - - void mask(bool red, bool green, bool blue, bool alpha) - { - glColorMask(red, green, blue, alpha); - } - - inline - void clear(const vec4& color) - { - p_color = color; - glClearColor(color[0], color[1], color[2], color[3]); - glClear(GL_COLOR_BUFFER_BIT); - } - - inline - void clear() - { - clear(p_color); - } - }; - -} - +#pragma once + +#include + +#ifndef __gl_h_ +#include +#endif + + + +namespace hpr::gpu +{ + + class ColorBuffer + { + + protected: + + bool p_enabledRed; + bool p_enabledGreen; + bool p_enabledBlue; + bool p_enabledAlpha; + vec4 p_color; + + public: + + inline + ColorBuffer() : + p_enabledRed {true}, + p_enabledGreen {true}, + p_enabledBlue {true}, + p_enabledAlpha {true}, + p_color {} + {} + + inline + ColorBuffer(bool red, bool green, bool blue, bool alpha) : + p_enabledRed {red}, + p_enabledGreen {green}, + p_enabledBlue {blue}, + p_enabledAlpha {alpha}, + p_color {} + {} + + virtual + ~ColorBuffer() = default; + + inline + void mask(bool red, bool green, bool blue, bool alpha) const + { + glColorMask(red, green, blue, alpha); + } + + inline + void clear(const vec4& color) + { + p_color = color; + glClearColor(color[0], color[1], color[2], color[3]); + glClear(GL_COLOR_BUFFER_BIT); + } + + inline + void clear() + { + clear(p_color); + } + }; + +} + diff --git a/source/hpr/gpu/common.hpp b/source/hpr/gpu/common.hpp index 2b382c7..03f83bc 100644 --- a/source/hpr/gpu/common.hpp +++ b/source/hpr/gpu/common.hpp @@ -1,4 +1,4 @@ -#pragma once - -#include - +#pragma once + +#include + diff --git a/source/hpr/gpu/context.hpp b/source/hpr/gpu/context.hpp new file mode 100644 index 0000000..1f9061f --- /dev/null +++ b/source/hpr/gpu/context.hpp @@ -0,0 +1,122 @@ +#pragma once + +#ifndef __gl_h_ +#include +#endif +#include + +#include +#include + + +namespace hpr::gpu +{ + +class Context +{ + +protected: + + bool p_glInitialized; + bool p_glfwInitialized; + +public: + + inline + Context() : + p_glInitialized {false}, + p_glfwInitialized {false} + { + if (glfwInit()) + p_glfwInitialized = true; + else + throw std::runtime_error("Cannot initialize GLFW context"); + + } + + void link() + { + if (gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) + p_glInitialized = true; + else + throw std::runtime_error("Cannot initialize GLAD context"); + } + + constexpr + bool valid() const + { + return p_glInitialized && p_glfwInitialized; + } + + inline + void destroy() const + { + glfwTerminate(); + } + + inline + void debug(bool enable = true) + { + const auto debugOutput = [](GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char* message, const void* userParam) + { + // ignore non-significant error/warning codes + if (id == 131169 || id == 131185 || id == 131218 || id == 131204) + return; + + std::cout << "Debug::GL[" << id << "]::"; + + switch (source) + { + case GL_DEBUG_SOURCE_API: std::cout << "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Window_System"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Shader_Compiler"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Third_Party"; break; + case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Application"; break; + case GL_DEBUG_SOURCE_OTHER: std::cout << "Other"; break; + default: break; + } + std::cout << "::"; + + switch (type) + { + case GL_DEBUG_TYPE_ERROR: std::cout << "Error";break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Deprecated_Behaviour"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Undefined_Behaviour"; break; + case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Portability"; break; + case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Performance"; break; + case GL_DEBUG_TYPE_MARKER: std::cout << "Marker"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Push_Group"; break; + case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Pop_Group"; break; + case GL_DEBUG_TYPE_OTHER: std::cout << "Other"; break; + default: break; + } + std::cout << " "; + + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: std::cout << "(high)"; break; + case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "(medium)"; break; + case GL_DEBUG_SEVERITY_LOW: std::cout << "(low)"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "(notification)"; break; + default: break; + } + std::cout << ": " << message << std::endl; + }; + + if (enable) + { + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(debugOutput, nullptr); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + } + else + { + glDisable(GL_DEBUG_OUTPUT); + glDisable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } + } + +}; + +} diff --git a/source/hpr/gpu/cull_face.hpp b/source/hpr/gpu/cull_face.hpp index e6e9faa..cd2c6a8 100644 --- a/source/hpr/gpu/cull_face.hpp +++ b/source/hpr/gpu/cull_face.hpp @@ -1,76 +1,80 @@ -#pragma once - -#ifndef __gl_h_ -#include -#endif - - - -namespace hpr::gpu -{ - -class CullFace -{ - - enum class Mode - { - Front = GL_FRONT, - Back = GL_BACK, - FrontAndBack = GL_FRONT_AND_BACK, - None = GL_NONE - }; - -protected: - - bool p_binded; - Mode p_mode; - -public: - - inline - CullFace() : - p_binded {false}, - p_mode {Mode::FrontAndBack} - {} - - inline - CullFace(Mode mode) : - p_binded {false}, - p_mode {mode} - {} - - virtual - ~CullFace() = default; - - inline - void bind() - { - p_binded = true; - glEnable(GL_CULL_FACE); - } - - inline - void unbind() - { - p_binded = false; - glDisable(GL_CULL_FACE); - - } - - inline - bool binded() const - { - return p_binded; - } - - inline - void set(Mode mode) - { - p_mode = mode; - glCullFace(static_cast(mode)); - } - -}; - -} - +#pragma once + +#ifndef __gl_h_ +#include +#endif + + + +namespace hpr::gpu +{ + +class CullFace +{ + +public: + + enum Mode + { + Front = GL_FRONT, + Back = GL_BACK, + FrontAndBack = GL_FRONT_AND_BACK, + None = GL_NONE + }; + +protected: + + bool p_binded; + Mode p_mode; + +public: + + inline + CullFace() : + p_binded {false}, + p_mode {Mode::FrontAndBack} + {} + + inline + CullFace(Mode mode) : + p_binded {false}, + p_mode {mode} + { + set(mode); + } + + virtual + ~CullFace() = default; + + inline + void bind() + { + p_binded = true; + glEnable(GL_CULL_FACE); + } + + inline + void unbind() + { + p_binded = false; + glDisable(GL_CULL_FACE); + + } + + inline + bool binded() const + { + return p_binded; + } + + inline + void set(Mode mode) + { + p_mode = mode; + glCullFace(static_cast(mode)); + } + +}; + +} + diff --git a/source/hpr/gpu/depth_buffer.hpp b/source/hpr/gpu/depth_buffer.hpp index 21187ee..19c3773 100644 --- a/source/hpr/gpu/depth_buffer.hpp +++ b/source/hpr/gpu/depth_buffer.hpp @@ -1,84 +1,84 @@ -#pragma once - -#ifndef __gl_h_ -#include -#endif - - -namespace hpr::gpu -{ - -class DepthBuffer -{ - -protected: - - bool p_enabled; - bool p_binded; - -public: - - inline - DepthBuffer() : - p_enabled {true}, - p_binded {false} - {} - - inline - DepthBuffer(bool enabled) : - p_enabled {enabled}, - p_binded {false} - {} - - virtual - ~DepthBuffer() = default; - - inline - void bind() - { - p_binded = true; - glEnable(GL_DEPTH_TEST); - } - - inline - void unbind() - { - p_binded = false; - glDisable(GL_DEPTH_TEST); - } - - inline - bool binded() const - { - return p_binded; - } - - inline - void enable() - { - p_enabled = true; - glDepthMask(GL_TRUE); - } - - inline - void disable() - { - p_enabled = false; - glDepthMask(GL_FALSE); - } - - [[nodiscard]] - inline - bool enabled() const - { - return p_enabled; - } - - inline - void clear() const - { - glClear(GL_DEPTH_BUFFER_BIT); - } -}; - -} +#pragma once + +#ifndef __gl_h_ +#include +#endif + + +namespace hpr::gpu +{ + +class DepthBuffer +{ + +protected: + + bool p_enabled; + bool p_binded; + +public: + + inline + DepthBuffer() : + p_enabled {true}, + p_binded {false} + {} + + inline + DepthBuffer(bool enabled) : + p_enabled {enabled}, + p_binded {false} + {} + + virtual + ~DepthBuffer() = default; + + inline + void bind() + { + p_binded = true; + glEnable(GL_DEPTH_TEST); + } + + inline + void unbind() + { + p_binded = false; + glDisable(GL_DEPTH_TEST); + } + + inline + bool binded() const + { + return p_binded; + } + + inline + void enable() + { + p_enabled = true; + glDepthMask(GL_TRUE); + } + + inline + void disable() + { + p_enabled = false; + glDepthMask(GL_FALSE); + } + + [[nodiscard]] + inline + bool enabled() const + { + return p_enabled; + } + + inline + void clear() const + { + glClear(GL_DEPTH_BUFFER_BIT); + } +}; + +} diff --git a/source/hpr/gpu/framebuffer.hpp b/source/hpr/gpu/framebuffer.hpp index 7534e65..05ba276 100644 --- a/source/hpr/gpu/framebuffer.hpp +++ b/source/hpr/gpu/framebuffer.hpp @@ -1,131 +1,178 @@ -#pragma once - -#include "texture.hpp" -#include "renderbuffer.hpp" - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - -class Framebuffer -{ - -protected: - - Texture p_texture; - Renderbuffer p_renderbuffer; - unsigned int p_index; - bool p_binded; - -public: - - inline - Framebuffer() : - p_index {0}, - p_texture {}, - p_renderbuffer {} - {} - - virtual - ~Framebuffer() = default; - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - void bind() - { - glBindFramebuffer(GL_FRAMEBUFFER, p_index); - p_binded = true; - } - - void unbind() - { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - p_binded = false; - } - - bool binded() const - { - return p_binded; - } - - void attach(const Texture& texture) - { - if (!binded() && valid()) - std::runtime_error("Framebuffer not binded or invalid"); - if (!texture.valid()) - std::runtime_error("Texture is not valid"); - - p_texture = texture; - p_texture.bind(); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_texture.index(), 0); - - p_texture.unbind(); - } - - void attach(const Renderbuffer& renderbuffer) - { - if (!binded() && valid()) - std::runtime_error("Framebuffer not binded or invalid"); - if (!renderbuffer.valid()) - std::runtime_error("Renderbuffer is not valid"); - - p_renderbuffer = renderbuffer; - p_renderbuffer.bind(); - p_renderbuffer.storage(p_texture.width(), p_texture.height()); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, p_renderbuffer.index()); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - throw std::runtime_error("Framebuffer is not complete"); - - p_renderbuffer.unbind(); - } - - void create() - { - glGenFramebuffers(1, &p_index); - bind(); - - if (!p_texture.valid()) - p_texture.create(); - attach(p_texture); - - if (!p_renderbuffer.valid()) - p_renderbuffer.create(); - attach(p_renderbuffer); - - unbind(); - } - - void rescale(int width, int height) - { - p_texture.bind(); - p_texture.rescale(width, height); - - p_renderbuffer.bind(); - p_renderbuffer.storage(p_texture.width(), p_texture.height()); - - p_texture.unbind(); - p_renderbuffer.unbind(); - } - - void destroy() - { - p_texture.destroy(); - p_renderbuffer.destroy(); - glDeleteFramebuffers(1, &p_index); - } - - bool valid() const - { - return p_index != 0; - } -}; - +#pragma once + +#include +#include + + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + +class Framebuffer +{ + +protected: + + Texture p_texture; + Renderbuffer p_renderbuffer; + unsigned int p_index; + bool p_binded; + +public: + + inline + Framebuffer() : + p_index {0}, + p_texture {1, 1}, // non zero + p_renderbuffer {} + {} + + virtual + ~Framebuffer() = default; + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + void bind() + { + glBindFramebuffer(GL_FRAMEBUFFER, p_index); + p_binded = true; + } + + void unbind() + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + p_binded = false; + } + + bool binded() const + { + return p_binded; + } + + void attach(const Texture& texture) + { + if (!binded() && valid()) + throw std::runtime_error("Framebuffer not binded or invalid"); + if (!texture.valid()) + throw std::runtime_error("Texture is not valid"); + + p_texture = texture; + p_texture.bind(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_texture.index(), 0); + + p_texture.unbind(); + } + + void attach(const Renderbuffer& renderbuffer) + { + if (!binded() && valid()) + throw std::runtime_error("Framebuffer not binded or invalid"); + if (!renderbuffer.valid()) + throw std::runtime_error("Renderbuffer is not valid"); + + p_renderbuffer = renderbuffer; + p_renderbuffer.bind(); + p_renderbuffer.storage(p_texture.width(), p_texture.height()); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, p_renderbuffer.index()); + + std::stringstream ss; + ss << "Framebuffer is not complete: " << glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw std::runtime_error(ss.str()); + + p_renderbuffer.unbind(); + } + + void create() + { + glGenFramebuffers(1, &p_index); + bind(); + + if (!p_texture.valid()) + { + p_texture.create(); + attach(p_texture); + } + + if (!p_renderbuffer.valid()) + { + p_renderbuffer.create(); + attach(p_renderbuffer); + } + + unbind(); + } + + void rescale() + { + p_texture.bind(); + p_texture.rescale(); + + p_renderbuffer.bind(); + p_renderbuffer.storage(p_texture.width(), p_texture.height()); + + p_texture.unbind(); + p_renderbuffer.unbind(); + } + + void rescale(int width, int height) + { + p_texture.bind(); + p_texture.rescale(width, height); + + p_renderbuffer.bind(); + p_renderbuffer.storage(p_texture.width(), p_texture.height()); + + p_texture.unbind(); + p_renderbuffer.unbind(); + } + + void destroy() + { + p_texture.destroy(); + p_renderbuffer.destroy(); + glDeleteFramebuffers(1, &p_index); + } + + [[nodiscard]] + bool valid() const + { + return p_index != 0; + } + + Texture& texture() + { + return p_texture; + } + + int& width() + { + return p_texture.width(); + } + + [[nodiscard]] + int width() const + { + return p_texture.width(); + } + + int& height() + { + return p_texture.height(); + } + + [[nodiscard]] + int height() const + { + return p_texture.height(); + } +}; + } \ No newline at end of file diff --git a/source/hpr/gpu/gpu.cpp b/source/hpr/gpu/gpu.cpp index d2e1102..e69de29 100644 --- a/source/hpr/gpu/gpu.cpp +++ b/source/hpr/gpu/gpu.cpp @@ -1,3 +0,0 @@ -// -// Created by L-Nafaryus on 12/16/2022. -// diff --git a/source/hpr/gpu/monitor.hpp b/source/hpr/gpu/monitor.hpp new file mode 100644 index 0000000..b0a35f7 --- /dev/null +++ b/source/hpr/gpu/monitor.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + + +namespace hpr::gpu +{ + +class Monitor +{ + +protected: + + GLFWmonitor* p_instance; + +public: + + inline + Monitor() : + p_instance {glfwGetPrimaryMonitor()} + {} + + virtual + ~Monitor() = default; + + inline + GLFWmonitor* instance() const + { + return p_instance; + } + +}; + +} \ No newline at end of file diff --git a/source/hpr/gpu/renderbuffer.hpp b/source/hpr/gpu/renderbuffer.hpp index b38c079..f4205a6 100644 --- a/source/hpr/gpu/renderbuffer.hpp +++ b/source/hpr/gpu/renderbuffer.hpp @@ -1,69 +1,69 @@ -#pragma once - -#ifndef __gl_h_ -#include -#endif - - - -namespace hpr::gpu -{ - - class Renderbuffer - { - - protected: - - unsigned int p_index; - - public: - - inline - Renderbuffer() : - p_index {0} - {} - - virtual - ~Renderbuffer() = default; - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - void bind() - { - glBindRenderbuffer(GL_RENDERBUFFER, p_index); - } - - void unbind() - { - glBindRenderbuffer(GL_RENDERBUFFER, 0); - } - - void create() - { - glGenRenderbuffers(1, &p_index); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, p_index); - } - - void storage(int width, int height) - { - glBindRenderbuffer(GL_RENDERBUFFER, p_index); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - } - - void destroy() - { - glDeleteRenderbuffers(1, &p_index); - } - - bool valid() const - { - return p_index != 0; - } - }; - -} +#pragma once + +#ifndef __gl_h_ +#include +#endif + + + +namespace hpr::gpu +{ + + class Renderbuffer + { + + protected: + + unsigned int p_index; + + public: + + inline + Renderbuffer() : + p_index {0} + {} + + virtual + ~Renderbuffer() = default; + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + void bind() + { + glBindRenderbuffer(GL_RENDERBUFFER, p_index); + } + + void unbind() + { + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + + void create() + { + glGenRenderbuffers(1, &p_index); + //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, p_index); + } + + void storage(int width, int height) + { + glBindRenderbuffer(GL_RENDERBUFFER, p_index); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + + void destroy() + { + glDeleteRenderbuffers(1, &p_index); + } + + bool valid() const + { + return p_index != 0; + } + }; + +} diff --git a/source/hpr/gpu/scene.hpp b/source/hpr/gpu/scene.hpp index 8049e89..e69de29 100644 --- a/source/hpr/gpu/scene.hpp +++ b/source/hpr/gpu/scene.hpp @@ -1,8 +0,0 @@ -// -// Created by L-Nafaryus on 12/16/2022. -// - -#ifndef HPR_SCENE_HPP -#define HPR_SCENE_HPP - -#endif //HPR_SCENE_HPP diff --git a/source/hpr/gpu/shader.hpp b/source/hpr/gpu/shader.hpp index 17380a0..b797498 100644 --- a/source/hpr/gpu/shader.hpp +++ b/source/hpr/gpu/shader.hpp @@ -1,129 +1,129 @@ -#pragma once - -#include - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - -class Shader -{ - -public: - - enum class Type - { - Vertex = GL_VERTEX_SHADER, - TessControl = GL_TESS_CONTROL_SHADER, - TessEvaluation = GL_TESS_EVALUATION_SHADER, - Geometry = GL_GEOMETRY_SHADER, - Fragment = GL_FRAGMENT_SHADER, - Compute = GL_COMPUTE_SHADER, - Unknown = -1 - }; - -protected: - - std::string p_filename; - std::string p_source; - std::string p_label; - Type p_type; - - unsigned int p_index; - -public: - - // Constructors - - inline - Shader() : - p_filename {}, - p_source {}, - p_label {}, - p_type {Type::Unknown}, - p_index {0} - {} - - inline - Shader(Type type) : - p_filename {}, - p_source {}, - p_label {}, - p_type {type} - {} - - inline - Shader(Type type, const std::string& source) : - p_filename {}, - p_source {source}, - p_label {}, - p_type {type}, - p_index {0} - {} - - virtual - ~Shader() = default; - - // Member functions - - [[nodiscard]] - std::string filename() const - { - return p_filename; - } - - [[nodiscard]] - std::string label() const - { - return p_label; - } - - [[nodiscard]] - Type type() const - { - return p_type; - } - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - void create(const std::string& label = "") - { - if (p_type == Type::Unknown) - throw std::runtime_error("Unknown shader type"); - - p_index = glCreateShader(static_cast(p_type)); - if (p_index == 0) - throw std::runtime_error("Cannot create shader"); - - const char* shaderSource = p_source.c_str(); - glShaderSource(p_index, 1, &shaderSource, nullptr); - GLenum result = glGetError(); - glCompileShader(p_index); - - int shaderStatus; - glGetShaderiv(p_index, GL_COMPILE_STATUS, &shaderStatus); - if (!shaderStatus) - { - char error[2048 + 1]; - glGetShaderInfoLog(p_index, 2048, nullptr, error); - - throw std::runtime_error(error); - } - - p_label = label; - } - - void destroy() - { - glDeleteShader(p_index); - } -}; - +#pragma once + +#include + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + +class Shader +{ + +public: + + enum class Type + { + Vertex = GL_VERTEX_SHADER, + TessControl = GL_TESS_CONTROL_SHADER, + TessEvaluation = GL_TESS_EVALUATION_SHADER, + Geometry = GL_GEOMETRY_SHADER, + Fragment = GL_FRAGMENT_SHADER, + Compute = GL_COMPUTE_SHADER, + Unknown = -1 + }; + +protected: + + std::string p_filename; + std::string p_source; + std::string p_label; + Type p_type; + + unsigned int p_index; + +public: + + // Constructors + + inline + Shader() : + p_filename {}, + p_source {}, + p_label {}, + p_type {Type::Unknown}, + p_index {0} + {} + + inline + Shader(Type type) : + p_filename {}, + p_source {}, + p_label {}, + p_type {type} + {} + + inline + Shader(Type type, const std::string& source) : + p_filename {}, + p_source {source}, + p_label {}, + p_type {type}, + p_index {0} + {} + + virtual + ~Shader() = default; + + // Member functions + + [[nodiscard]] + std::string filename() const + { + return p_filename; + } + + [[nodiscard]] + std::string label() const + { + return p_label; + } + + [[nodiscard]] + Type type() const + { + return p_type; + } + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + void create(const std::string& label = "") + { + if (p_type == Type::Unknown) + throw std::runtime_error("Unknown shader type"); + + p_index = glCreateShader(static_cast(p_type)); + if (p_index == 0) + throw std::runtime_error("Cannot create shader"); + + const char* shaderSource = p_source.c_str(); + glShaderSource(p_index, 1, &shaderSource, nullptr); + GLenum result = glGetError(); + glCompileShader(p_index); + + int shaderStatus; + glGetShaderiv(p_index, GL_COMPILE_STATUS, &shaderStatus); + if (!shaderStatus) + { + char error[2048 + 1]; + glGetShaderInfoLog(p_index, 2048, nullptr, error); + + throw std::runtime_error(error); + } + + p_label = label; + } + + void destroy() + { + glDeleteShader(p_index); + } +}; + } \ No newline at end of file diff --git a/source/hpr/gpu/shader_program.hpp b/source/hpr/gpu/shader_program.hpp index c5d76cb..cd8b2ef 100644 --- a/source/hpr/gpu/shader_program.hpp +++ b/source/hpr/gpu/shader_program.hpp @@ -1,96 +1,268 @@ -#pragma once - -#include "../containers.hpp" - -#include - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - - class ShaderProgram - { - - protected: - - unsigned int p_index; - darray p_shaders; - - public: - - // Constructors - - inline - ShaderProgram() : - p_index {0} - {} - - virtual - ~ShaderProgram() = default; - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - darray shaders() - { - return p_shaders; - } - - void create(const std::string& label = "") - { - p_index = glCreateProgram(); - } - - void attach(const Shader& shader) - { - glAttachShader(p_index, shader.index()); - p_shaders.push(shader); - } - - void detach(const Shader& shader) - { - // WARNING: segfault, destroy_at (char) - p_shaders.remove([shader](const Shader& _shader) - { - return shader.index() == _shader.index(); - }); - - glDetachShader(p_index, shader.index()); - } - - void link() - { - glLinkProgram(p_index); - - GLint status; - glGetProgramiv(p_index, GL_LINK_STATUS, &status); - - if (status == GL_FALSE) - throw std::runtime_error("Shader program link error"); - } - - void destroy() - { - //for (auto& shader : p_shaders) - // detach(shader); - glDeleteShader(p_index); - } - - void bind() - { - glUseProgram(p_index); - } - - void unbind() - { - glUseProgram(0); - } - }; - -} +#pragma once + +#include +#include +#include + +#include + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + + struct ShaderProgramLinkError : public Exception + { + inline explicit ShaderProgramLinkError(std::source_location location = std::source_location::current()) : Exception {"Shader program link error", location} {} + inline explicit ShaderProgramLinkError(const std::string& message, std::source_location location = std::source_location::current()) : Exception {message, location} {} + }; + + class ShaderProgram + { + + protected: + + unsigned int p_index; + + public: + + // Constructors + + inline + ShaderProgram() : + p_index {0} + {} + + virtual + ~ShaderProgram() = default; + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + void create() + { + p_index = glCreateProgram(); + } + + void attach(const Shader& shader) + { + glAttachShader(p_index, shader.index()); + } + + void detach(const Shader& shader) + { + glDetachShader(p_index, shader.index()); + } + + void link() + { + glLinkProgram(p_index); + + GLint status; + glGetProgramiv(p_index, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) + { + GLsizei log_length = 0; + GLchar message[1024]; + glGetProgramInfoLog(p_index, 1024, &log_length, message); + throw ShaderProgramLinkError(message); + } + } + + void destroy() + { + glDeleteProgram(p_index); + } + + void bind() + { + glUseProgram(p_index); + } + + void unbind() + { + glUseProgram(0); + } + + inline + int uniformLocation(const std::string& label) const + { + return glGetUniformLocation(p_index, label.c_str()); + } + + template ... Args> + inline + void uniformValue(int location, T value, Args ...args) + { + auto arr = {value, static_cast(args)...}; + if constexpr (std::is_same::value) + { + switch (arr.size()) + { + case 1: glUniform1i(location, arr[0]); break; + case 2: glUniform2i(location, arr[0], arr[1]); break; + case 3: glUniform3i(location, arr[0], arr[1], arr[2]); break; + case 4: glUniform4i(location, arr[0], arr[1], arr[2], arr[3]); break; + } + } + else if constexpr (std::is_same::value) + { + switch (arr.size()) + { + case 1: glUniform1ui(location, arr[0]); break; + case 2: glUniform2ui(location, arr[0], arr[1]); break; + case 3: glUniform3ui(location, arr[0], arr[1], arr[2]); break; + case 4: glUniform4ui(location, arr[0], arr[1], arr[2], arr[3]); break; + } + } + else if constexpr (std::is_same::value) + { + switch (arr.size()) + { + case 1: glUniform1f(location, arr[0]); break; + case 2: glUniform2f(location, arr[0], arr[1]); break; + case 3: glUniform3f(location, arr[0], arr[1], arr[2]); break; + case 4: glUniform4f(location, arr[0], arr[1], arr[2], arr[3]); break; + } + } + else if constexpr (std::is_same::value) + { + switch (arr.size()) + { + case 1: glUniform1d(location, arr[0]); break; + case 2: glUniform2d(location, arr[0], arr[1]); break; + case 3: glUniform3d(location, arr[0], arr[1], arr[2]); break; + case 4: glUniform4d(location, arr[0], arr[1], arr[2], arr[3]); break; + } + } + else + throw std::runtime_error("Unsupported value type"); + } + + template ... Args> + inline + void uniformValue(const std::string& label, T value, Args ...args) + { + uniformValue(uniformLocation(label), value, std::forward(args)...); + } + + template + inline + void uniformVector(int location, hpr::Size size, T* data) + { + if constexpr (std::is_same::value) + { + switch (S) + { + case 1: glUniform1iv(location, size, data); break; + case 2: glUniform2iv(location, size, data); break; + case 3: glUniform3iv(location, size, data); break; + case 4: glUniform4iv(location, size, data); break; + } + } + else if constexpr (std::is_same::value) + { + switch (S) + { + case 1: glUniform1uiv(location, size, data); break; + case 2: glUniform2uiv(location, size, data); break; + case 3: glUniform3uiv(location, size, data); break; + case 4: glUniform4uiv(location, size, data); break; + } + } + else if constexpr (std::is_same::value) + { + switch (S) + { + case 1: glUniform1fv(location, size, data); break; + case 2: glUniform2fv(location, size, data); break; + case 3: glUniform3fv(location, size, data); break; + case 4: glUniform4fv(location, size, data); break; + } + } + else if constexpr (std::is_same::value) + { + switch (size) + { + case 1: glUniform1dv(location, size, data); break; + case 2: glUniform2dv(location, size, data); break; + case 3: glUniform3dv(location, size, data); break; + case 4: glUniform4dv(location, size, data); break; + } + } + else + throw std::runtime_error("Unsupported value type"); + } + + template + inline + void uniformVector(const std::string& label, hpr::Size size, T* data) + { + uniformVector(uniformLocation(label), size, data); + } + + template + inline + void uniformMatrix(int location, hpr::Size size, bool transpose, T* data) + { + if constexpr (std::is_same::value) + { + if constexpr (C == 2 && R == 2) + glUniformMatrix2fv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 3) + glUniformMatrix3fv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 4) + glUniformMatrix4fv(location, static_cast(size), transpose, data); + else if constexpr (C == 2 && R == 3) + glUniformMatrix2x3fv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 2) + glUniformMatrix3x2fv(location, static_cast(size), transpose, data); + else if constexpr (C == 2 && R == 4) + glUniformMatrix2x4fv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 2) + glUniformMatrix4x2fv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 4) + glUniformMatrix3x4fv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 3) + glUniformMatrix4x3fv(location, static_cast(size), transpose, data); + } + else if constexpr (std::is_same::value) + { + if constexpr (C == 2 && R == 2) + glUniformMatrix2dv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 3) + glUniformMatrix3dv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 4) + glUniformMatrix4dv(location, static_cast(size), transpose, data); + else if constexpr (C == 2 && R == 3) + glUniformMatrix2x3dv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 2) + glUniformMatrix3x2dv(location, static_cast(size), transpose, data); + else if constexpr (C == 2 && R == 4) + glUniformMatrix2x4dv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 2) + glUniformMatrix4x2dv(location, static_cast(size), transpose, data); + else if constexpr (C == 3 && R == 4) + glUniformMatrix3x4dv(location, static_cast(size), transpose, data); + else if constexpr (C == 4 && R == 3) + glUniformMatrix4x3dv(location, static_cast(size), transpose, data); + } + else + throw std::runtime_error("Unsupported value type"); + } + + template + inline + void uniformMatrix(const std::string& label, hpr::Size size, bool transpose, T* data) + { + uniformMatrix(uniformLocation(label), size, transpose, data); + } + }; + +} diff --git a/source/hpr/gpu/shaders/test.frag.glsl b/source/hpr/gpu/shaders/test.frag.glsl index eef439c..edb04f7 100644 --- a/source/hpr/gpu/shaders/test.frag.glsl +++ b/source/hpr/gpu/shaders/test.frag.glsl @@ -1,9 +1,9 @@ -#version 330 -uniform vec3 color; - -out vec4 out_color; - -void main(void) -{ - out_color = vec4(color, 0.5f); +#version 330 +uniform vec3 color; + +out vec4 out_color; + +void main(void) +{ + out_color = vec4(color, 0.5f); } \ No newline at end of file diff --git a/source/hpr/gpu/shaders/test.vert.glsl b/source/hpr/gpu/shaders/test.vert.glsl index cc518f1..658f988 100644 --- a/source/hpr/gpu/shaders/test.vert.glsl +++ b/source/hpr/gpu/shaders/test.vert.glsl @@ -1,11 +1,11 @@ -#version 330 core -layout (location = 0) in vec3 aPos; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); +#version 330 core +layout (location = 0) in vec3 aPos; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); } \ No newline at end of file diff --git a/source/hpr/gpu/stencil_buffer.hpp b/source/hpr/gpu/stencil_buffer.hpp index c7bbb65..414121a 100644 --- a/source/hpr/gpu/stencil_buffer.hpp +++ b/source/hpr/gpu/stencil_buffer.hpp @@ -1,84 +1,84 @@ -#pragma once - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - - class StencilBuffer - { - - protected: - - bool p_enabled; - bool p_binded; - - public: - - inline - StencilBuffer() : - p_enabled {false}, - p_binded {false} - {} - - inline - StencilBuffer(bool enabled) : - p_enabled {enabled}, - p_binded {false} - {} - - virtual - ~StencilBuffer() = default; - - - inline - void bind() - { - p_binded = true; - glEnable(GL_STENCIL_TEST); - } - - inline - void unbind() - { - p_binded = false; - glDisable(GL_STENCIL_TEST); - } - - inline - bool binded() const - { - return p_binded; - } - - inline - void enable() - { - p_enabled = true; - glStencilMask(GL_TRUE); - } - - inline - void disable() - { - p_enabled = false; - glStencilMask(GL_FALSE); - } - - [[nodiscard]] - inline - bool enabled() const - { - return p_enabled; - } - - inline - void clear() const - { - glClear(GL_STENCIL_BUFFER_BIT); - } - }; - -} +#pragma once + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + + class StencilBuffer + { + + protected: + + bool p_enabled; + bool p_binded; + + public: + + inline + StencilBuffer() : + p_enabled {false}, + p_binded {false} + {} + + inline + StencilBuffer(bool enabled) : + p_enabled {enabled}, + p_binded {false} + {} + + virtual + ~StencilBuffer() = default; + + + inline + void bind() + { + p_binded = true; + glEnable(GL_STENCIL_TEST); + } + + inline + void unbind() + { + p_binded = false; + glDisable(GL_STENCIL_TEST); + } + + inline + bool binded() const + { + return p_binded; + } + + inline + void enable() + { + p_enabled = true; + glStencilMask(GL_TRUE); + } + + inline + void disable() + { + p_enabled = false; + glStencilMask(GL_FALSE); + } + + [[nodiscard]] + inline + bool enabled() const + { + return p_enabled; + } + + inline + void clear() const + { + glClear(GL_STENCIL_BUFFER_BIT); + } + }; + +} diff --git a/source/hpr/gpu/texture.hpp b/source/hpr/gpu/texture.hpp index 0194f5b..6de71e8 100644 --- a/source/hpr/gpu/texture.hpp +++ b/source/hpr/gpu/texture.hpp @@ -1,193 +1,223 @@ -#pragma once - -#include "../containers.hpp" - -#include -#include -#define STB_IMAGE_IMPLEMENTATION -#include - -#ifndef __gl_h_ -#include -#endif - -namespace hpr::gpu -{ - - class Texture - { - - public: - - using ustring = std::basic_string; - - public: - - enum class Format - { - RGB = GL_RGB, - RGBA = GL_RGBA - }; - - protected: - - unsigned int p_index; - std::string p_filename; - ustring p_source; - - int p_width; - int p_height; - Format p_internalFormat; - Format p_imageFormat; - - public: - - inline - Texture() : - p_index {0}, - p_filename {}, - p_source {}, - p_width {}, - p_height {}, - p_internalFormat {Format::RGBA}, - p_imageFormat {Format::RGBA} - {} - - inline - Texture(const std::string& filename) : - p_index {0}, - p_filename {filename}, - p_source {}, - p_width {}, - p_height {}, - p_internalFormat {Format::RGB}, - p_imageFormat {Format::RGB} - {} - - virtual - ~Texture() = default; - - [[nodiscard]] - unsigned int index() const - { - return p_index; - } - - [[nodiscard]] - unsigned int width() const - { - return p_width; - } - - [[nodiscard]] - unsigned int height() const - { - return p_height; - } - - void active() - { - glActiveTexture(GL_TEXTURE0 + p_index); - } - - void bind() - { - glBindTexture(GL_TEXTURE_2D, p_index); - } - - void unbind() - { - glBindTexture(GL_TEXTURE_2D, 0); - } - - void alphaChannel(bool enabled) - { - if (enabled) - { - p_internalFormat = Format::RGBA; - p_imageFormat = Format::RGBA; - } - else - { - p_internalFormat = Format::RGB; - p_imageFormat = Format::RGB; - } - } - - void load(const std::string& filename) - { - auto filepath = std::filesystem::canonical(std::filesystem::path(filename)).string(); - - stbi_set_flip_vertically_on_load(true); - int channelsCount; - unsigned char* source = stbi_load(filepath.c_str(), &p_width, &p_height, &channelsCount, 0); - - if (!source) - throw std::runtime_error("Failed to load texture source"); - else - create(source); - - stbi_image_free(source); - } - - void load() - { - load(p_filename); - } - - void create() - { - glGenTextures(1, &p_index); - bind(); - - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, nullptr); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - unbind(); - } - - void create(const ustring& source) - { - glGenTextures(1, &p_index); - bind(); - - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, source.data()); - - p_source = source; - - glGenerateMipmap(GL_TEXTURE_2D); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - unbind(); - } - - void rescale(int width, int height) - { - bind(); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, !p_source.empty() ? p_source.data() : nullptr); - unbind(); - } - - void destroy() - { - glDeleteTextures(1, &p_index); - } - - bool valid() const - { - return p_index != 0; - } - }; - -} - +#pragma once + +#include + +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include + +#ifndef __gl_h_ +#include +#endif + +namespace hpr::gpu +{ + + class Texture + { + + public: + + using ustring = std::basic_string; + + public: + + enum class Format + { + RGB = GL_RGB, + RGBA = GL_RGBA + }; + + protected: + + unsigned int p_index; + std::string p_filename; + ustring p_source; + + int p_width; + int p_height; + Format p_internalFormat; + Format p_imageFormat; + + public: + + inline + Texture() : + p_index {0}, + p_filename {}, + p_source {}, + p_width {}, + p_height {}, + p_internalFormat {Format::RGBA}, + p_imageFormat {Format::RGBA} + {} + + inline + Texture(int width, int height) : + p_index {0}, + p_filename {}, + p_source {}, + p_width {width}, + p_height {height}, + p_internalFormat {Format::RGBA}, + p_imageFormat {Format::RGBA} + {} + + inline + Texture(const std::string& filename) : + p_index {0}, + p_filename {filename}, + p_source {}, + p_width {}, + p_height {}, + p_internalFormat {Format::RGB}, + p_imageFormat {Format::RGB} + {} + + virtual + ~Texture() = default; + + [[nodiscard]] + unsigned int index() const + { + return p_index; + } + + int& width() + { + return p_width; + } + + [[nodiscard]] + int width() const + { + return p_width; + } + + int& height() + { + return p_height; + } + + [[nodiscard]] + int height() const + { + return p_height; + } + + void active() const + { + glActiveTexture(GL_TEXTURE0 + p_index); + } + + void bind() const + { + glBindTexture(GL_TEXTURE_2D, p_index); + } + + void unbind() const + { + glBindTexture(GL_TEXTURE_2D, 0); + } + + void alphaChannel(bool enabled) + { + if (enabled) + { + p_internalFormat = Format::RGBA; + p_imageFormat = Format::RGBA; + } + else + { + p_internalFormat = Format::RGB; + p_imageFormat = Format::RGB; + } + } + + void load(const std::string& filename) + { + auto filepath = std::filesystem::canonical(std::filesystem::path(filename)).string(); + + stbi_set_flip_vertically_on_load(true); + int channelsCount; + unsigned char* source = stbi_load(filepath.c_str(), &p_width, &p_height, &channelsCount, 0); + + if (!source) + throw std::runtime_error("Failed to load texture source"); + else + create(source); + + stbi_image_free(source); + } + + void load() + { + load(p_filename); + } + + void create() + { + glGenTextures(1, &p_index); + bind(); + + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + unbind(); + } + + void create(const ustring& source) + { + glGenTextures(1, &p_index); + bind(); + + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, source.data()); + + p_source = source; + + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + unbind(); + } + + void rescale() + { + bind(); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, !p_source.empty() ? p_source.data() : nullptr); + unbind(); + } + + void rescale(int width, int height) + { + p_width = width; + p_height = height; + bind(); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)p_internalFormat, p_width, p_height, 0, (GLint)p_imageFormat, GL_UNSIGNED_BYTE, !p_source.empty() ? p_source.data() : nullptr); + unbind(); + } + + void destroy() + { + glDeleteTextures(1, &p_index); + } + + bool valid() const + { + return p_index != 0; + } + }; + +} + diff --git a/source/hpr/gpu/viewer.hpp b/source/hpr/gpu/viewer.hpp index 1cf0c0f..e69de29 100644 --- a/source/hpr/gpu/viewer.hpp +++ b/source/hpr/gpu/viewer.hpp @@ -1,8 +0,0 @@ -// -// Created by L-Nafaryus on 12/16/2022. -// - -#ifndef HPR_VIEWER_HPP -#define HPR_VIEWER_HPP - -#endif //HPR_VIEWER_HPP diff --git a/source/hpr/gpu/viewport.hpp b/source/hpr/gpu/viewport.hpp index 4e71440..bf6fe0a 100644 --- a/source/hpr/gpu/viewport.hpp +++ b/source/hpr/gpu/viewport.hpp @@ -1,45 +1,56 @@ -#pragma once - -#include "../math/vector.hpp" -#ifndef __gl_h_ -#include -#endif - - -namespace hpr::gpu -{ - -class Viewport -{ - -protected: - - vec2 p_pos; - vec2 p_size; - -public: - - inline - Viewport() : - p_pos {0.f, 0.f}, - p_size {0.f, 0.f} - {} - - inline - Viewport(const vec2& pos, const vec2& size) : - p_pos {pos}, - p_size {size} - {} - - virtual - ~Viewport() = default; - - inline - void set() - { - glViewport(p_pos[0], p_pos[1], p_size[0], p_size[1]); - } - -}; - +#pragma once + +#include + +#ifndef __gl_h_ +#include +#endif + + +namespace hpr::gpu +{ + +class Viewport +{ + +protected: + + vec2 p_pos; + vec2 p_size; + +public: + + inline + Viewport() : + p_pos {0.f, 0.f}, + p_size {0.f, 0.f} + {} + + inline + Viewport(const vec2& pos, const vec2& size) : + p_pos {pos}, + p_size {size} + {} + + virtual + ~Viewport() = default; + + vec2& pos() + { + return p_pos; + } + + vec2& size() + { + return p_size; + } + + inline + void set() + { + glViewport(p_pos[0], p_pos[1], p_size[0], p_size[1]); + } + +}; + } \ No newline at end of file diff --git a/source/hpr/gpu/window.hpp b/source/hpr/gpu/window.hpp new file mode 100644 index 0000000..4bfc12f --- /dev/null +++ b/source/hpr/gpu/window.hpp @@ -0,0 +1,296 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + + +namespace hpr::gpu +{ + +class Window +{ + +public: + + enum State + { + Visible, + Hidden, + Maximized, + Minimized, + Closed, + }; + + enum Style + { + Windowed, + Fullscreen, + Popup + }; + +protected: + + int p_posX; + int p_posY; + int p_width; + int p_height; + std::string p_title; + + State p_state; + Style p_style; + Window* p_parent; + Monitor* p_monitor; + + bool p_isActive; + bool p_isResizing; + + GLFWwindow* p_instance; + Context p_context; + +public: + + inline + Window() : + p_posX {0}, + p_posY {0}, + p_width {0}, + p_height {0}, + p_title {}, + p_state {State::Hidden}, + p_style {Style::Windowed}, + p_parent {nullptr}, + p_monitor {nullptr}, + p_isActive {true}, + p_isResizing {false}, + p_context {} + { + p_instance = glfwCreateWindow(p_width, p_height, p_title.c_str(), p_monitor != nullptr ? p_monitor->instance() : nullptr, p_parent != nullptr ? p_parent->p_instance : nullptr); + if (p_instance == nullptr) + throw std::runtime_error("Cannot create Window"); + + glfwMakeContextCurrent(p_instance); + p_context.link(); + + glfwSetWindowUserPointer(p_instance, this); + + glfwSetWindowPos(p_instance, p_posX, p_posY); + + this->state(p_state); + this->style(p_style); + } + + inline + Window(int x, int y, int width, int height, const std::string& title, Style style, Window* parent, Monitor* monitor) : + p_posX {x}, + p_posY {y}, + p_width {width}, + p_height {height}, + p_title {title}, + p_state {State::Visible}, + p_style {style}, + p_parent {parent}, + p_monitor {monitor}, + p_isActive {true}, + p_isResizing {false}, + p_context {} + { + //glfwInit(); + + p_instance = glfwCreateWindow(p_width, p_height, p_title.c_str(), p_monitor != nullptr ? p_monitor->instance() : nullptr, p_parent != nullptr ? p_parent->p_instance : nullptr); + if (p_instance == nullptr) + throw std::runtime_error("Cannot create window"); + + glfwMakeContextCurrent(p_instance); + p_context.link(); + + glfwSetWindowUserPointer(p_instance, this); + + glfwSetWindowPos(p_instance, p_posX, p_posY); + + this->state(p_state); + this->style(p_style); + } + + virtual + ~Window() + { + p_context.destroy(); + } + + // member functions + + inline + GLFWwindow* instance() const + { + return p_instance; + } + + inline + Context& context() + { + return p_context; + } + + inline + void pos(int x, int y) + { + p_posX = x; + p_posY = y; + } + + inline + int x() const + { + return p_posX; + } + + inline + int y() const + { + return p_posY; + } + + inline + void size(int width, int height) + { + p_width = width; + p_height = height; + } + + inline + int width() const + { + return p_width; + } + + inline + int height() const + { + return p_height; + } + + inline + void title(const std::string& title) + { + p_title = title; + } + + inline + std::string title() const + { + return p_title; + } + + inline + void state(State state) + { + switch (state) + { + case State::Visible: + glfwShowWindow(p_instance); + break; + case State::Hidden: + glfwHideWindow(p_instance); + break; + case State::Maximized: + glfwMaximizeWindow(p_instance); + break; + case State::Minimized: + glfwIconifyWindow(p_instance); + break; + case State::Closed: + glfwSetWindowShouldClose(p_instance, GLFW_TRUE); + break; + default: + break; + } + p_state = state; + } + + inline + State state() const + { + return p_state; + } + + inline + void style(Style style) + { + switch (style) + { + case Style::Windowed: + glfwSetWindowMonitor(p_instance, nullptr, p_posX, p_posY, p_width, p_height, GLFW_DONT_CARE); + break; + case Style::Fullscreen: + glfwSetWindowMonitor(p_instance, glfwGetPrimaryMonitor(), p_posX, p_posY, p_width, p_height, GLFW_DONT_CARE); + break; + default: + break; + } + p_style = style; + } + + inline + Style style() const + { + return p_style; + } + + inline + void destroy() + { + glfwDestroyWindow(p_instance); + p_instance = nullptr; + } + + inline + void swapBuffers() + { + glfwSwapBuffers(p_instance); + } + + inline + void pollEvents() + { + glfwPollEvents(); + } + +protected: + + std::function onFramebufferResize; + std::function onKeyClick; + +public: + + void framebufferResizeCallback(const std::function& event) + { + onFramebufferResize = event; + + glfwSetFramebufferSizeCallback(p_instance, [](GLFWwindow* window, int width, int height) + { + auto p = static_cast(glfwGetWindowUserPointer(window)); + if (p->onFramebufferResize) + p->onFramebufferResize(p, width, height); + }); + } + + void keyClickCallback(const std::function& event) + { + onKeyClick = event; + + glfwSetKeyCallback(p_instance, [](GLFWwindow* window, int key, int scancode, int action, int mods) + { + auto p = static_cast(glfwGetWindowUserPointer(window)); + if (p->onKeyClick) + p->onKeyClick(p, key, scancode, action, mods); + }); + } +}; + +} \ No newline at end of file diff --git a/source/hpr/hpr.cpp b/source/hpr/hpr.cpp index 40aea07..f1db6ee 100644 --- a/source/hpr/hpr.cpp +++ b/source/hpr/hpr.cpp @@ -1 +1 @@ -#include "hpr.hpp" \ No newline at end of file +#include "hpr.hpp" diff --git a/source/hpr/hpr.hpp b/source/hpr/hpr.hpp index f3344d7..83308c9 100644 --- a/source/hpr/hpr.hpp +++ b/source/hpr/hpr.hpp @@ -1,49 +1,37 @@ -#pragma once - - -namespace hpr -{ - /* Core */ - // containers - // math - // io - - /* Graphics */ - namespace gpu - { - // gpu - // window_system - } - - /* Mesh */ - namespace mesh - { - // mesh - } - - /* CSG */ - namespace csg - { - // csg - } -} - -#include "containers.hpp" -#include "math.hpp" -#include "io.hpp" - -#if WITH_GPU -#include "gpu.hpp" -#endif - -#if WITH_WS -#include "window_system.hpp" -#endif - -#if WITH_MESH -#include "mesh.hpp" -#endif - -#if WITH_CSG -#include "csg.hpp" -#endif +#pragma once + +#define HPR_VERSION_MAJOR 0 +#define HPR_VERSION_MINOR 10 +#define HPR_VERSION_PATCH 0 + +#if HPR_WITH_CONTAINERS +#include +#endif + +#if HPR_WITH_MATH +#include +#endif + +#if HPR_WITH_IO +#include +#endif + +#if HPR_WITH_GPU +#include +#endif + +#if HPR_WITH_MESH +#include +#endif + +#if HPR_WITH_CSG +#include +#endif + +#if HPR_WITH_GEOMETRY +#include +#endif + +#if HPR_WITH_PARALLEL +#include +#endif \ No newline at end of file diff --git a/source/hpr/io.hpp b/source/hpr/io.hpp index b0e8169..2aef663 100644 --- a/source/hpr/io.hpp +++ b/source/hpr/io.hpp @@ -1,3 +1,3 @@ -#pragma once - -#include "io/file.hpp" +#pragma once + +#include diff --git a/source/hpr/io/CMakeLists.txt b/source/hpr/io/CMakeLists.txt index 6085647..63d22a7 100644 --- a/source/hpr/io/CMakeLists.txt +++ b/source/hpr/io/CMakeLists.txt @@ -1,68 +1,27 @@ -cmake_minimum_required(VERSION 3.16) - -project(io - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX - ) - -add_library(${PROJECT_NAME}) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../io.hpp" "*.hpp" - ) - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES - "*.cpp" - ) - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ - PRIVATE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES} - ) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) - - -if(HPR_TEST) - add_subdirectory(tests) -endif() +cmake_minimum_required(VERSION 3.16) + +project(io + VERSION "${HPR_VERSION}" + LANGUAGES CXX +) + +hpr_add_library(${PROJECT_NAME} STATIC) + +hpr_collect_interface(${PROJECT_NAME} + "../io.hpp" + "*.hpp" +) + +hpr_collect_sources(${PROJECT_NAME} + "*.cpp" +) + +target_sources(${PROJECT_NAME} + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> + PRIVATE ${${PROJECT_NAME}_SOURCES} +) + + +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) +hpr_tests(${PROJECT_NAME} tests) + diff --git a/source/hpr/io/file.cpp b/source/hpr/io/file.cpp index dfca020..82577ed 100644 --- a/source/hpr/io/file.cpp +++ b/source/hpr/io/file.cpp @@ -1,70 +1,70 @@ - -#include "file.hpp" - - -namespace hpr -{ - -File::File() : - p_name {"\0"}, - p_mode {File::Read} -{} - -File::File(const std::string &filename) : - p_name {filename}, - p_mode {File::Read} -{} - -File::File(const char *filename) : - p_name {filename}, - p_mode {File::Read} -{} - -File::~File() -{ - if (p_file.is_open()) - p_file.close(); -} - -void File::open(const std::string &filename, unsigned int filemode) -{ - std::ios::openmode mode = filemode & File::Read ? std::ios::in : std::ios::out; - - if (filemode & File::Binary) - mode |= std::ios::binary; - - p_file.open(filename, mode); - - if (!p_file.is_open()) - throw std::ios::failure("Could not open file"); -} - -void File::close() -{ - if (p_file.is_open()) - p_file.close(); -} - -size_t File::length() -{ - std::streampos size = 0; - std::streampos oldPos = p_file.tellg(); - - p_file.seekg(0, std::ios::beg); - size = p_file.tellg(); - p_file.seekg(0, std::ios::end); - size = p_file.tellg() - size; - p_file.seekg(oldPos, std::ios::beg); - - return (size_t)size; -} - -std::stringstream File::read() -{ - std::stringstream content; - content << p_file.rdbuf(); - - return content; -} - + +#include + + +namespace hpr +{ + +File::File() : + p_name {"\0"}, + p_mode {File::Read} +{} + +File::File(const std::string &filename) : + p_name {filename}, + p_mode {File::Read} +{} + +File::File(const char *filename) : + p_name {filename}, + p_mode {File::Read} +{} + +File::~File() +{ + if (p_file.is_open()) + p_file.close(); +} + +void File::open(const std::string &filename, unsigned int filemode) +{ + std::ios::openmode mode = filemode & File::Read ? std::ios::in : std::ios::out; + + if (filemode & File::Binary) + mode |= std::ios::binary; + + p_file.open(filename, mode); + + if (!p_file.is_open()) + throw std::ios::failure("Could not open file"); +} + +void File::close() +{ + if (p_file.is_open()) + p_file.close(); +} + +size_t File::length() +{ + std::streampos size = 0; + std::streampos oldPos = p_file.tellg(); + + p_file.seekg(0, std::ios::beg); + size = p_file.tellg(); + p_file.seekg(0, std::ios::end); + size = p_file.tellg() - size; + p_file.seekg(oldPos, std::ios::beg); + + return (size_t)size; +} + +std::stringstream File::read() +{ + std::stringstream content; + content << p_file.rdbuf(); + + return content; +} + } \ No newline at end of file diff --git a/source/hpr/io/file.hpp b/source/hpr/io/file.hpp index 320ede0..9de5b99 100644 --- a/source/hpr/io/file.hpp +++ b/source/hpr/io/file.hpp @@ -1,53 +1,53 @@ -#pragma once - -#include -#include -#include -#include - -namespace hpr -{ - -class File -{ - -public: - - enum FileMode - { - Write = std::ios::out, - Read = std::ios::in, - Binary = std::ios::binary, - Append = std::ios::app - }; - -protected: - - std::string p_name; - unsigned int p_mode; - std::fstream p_file; - -public: - - // Constructors - - File(); - - File(const std::string& filename); - - File(const char* filename); - - ~File(); - - // Member functions - - void open(const std::string& filename, unsigned int filemode = File::Read); - - void close(); - - size_t length(); - - std::stringstream read(); -}; - -} +#pragma once + +#include +#include +#include +#include + +namespace hpr +{ + +class File +{ + +public: + + enum FileMode + { + Write = std::ios::out, + Read = std::ios::in, + Binary = std::ios::binary, + Append = std::ios::app + }; + +protected: + + std::string p_name; + unsigned int p_mode; + std::fstream p_file; + +public: + + // Constructors + + File(); + + File(const std::string& filename); + + File(const char* filename); + + ~File(); + + // Member functions + + void open(const std::string& filename, unsigned int filemode = File::Read); + + void close(); + + size_t length(); + + std::stringstream read(); +}; + +} diff --git a/source/hpr/io/logger.hpp b/source/hpr/io/logger.hpp index b6f8ece..0192a7c 100644 --- a/source/hpr/io/logger.hpp +++ b/source/hpr/io/logger.hpp @@ -1,274 +1,554 @@ -#pragma once - -#include "../containers/array.hpp" -#include -#include -#include -#include - -namespace hpr -{ - - -namespace logging -{ - enum Severity - { - Emergency, - Alert, - Critical, - Error, - Warning, - Notice, - Info, - Debug - }; - - class Sink - { - //friend class Logger; - - protected: - - Severity p_severity; - std::string_view p_message; - - public: - - Sink() : - p_severity {Emergency}, - p_message {} - {} - - Sink(Severity severity) : - p_severity {severity}, - p_message {} - {} - - void addMessage(const std::string_view& message) - { - p_message = message; - } - - virtual - void flush() = 0; - - - virtual - ~Sink() = default; - - }; - - class StandardOutput : public Sink - { - - public: - - StandardOutput() : - Sink() - {} - - explicit - StandardOutput(Severity severity) : - Sink(severity) - {} - - void flush() override - { - if (p_severity < Error) - std::cerr << p_message << "\n"; - if (p_severity < Debug) - std::cout << p_message << "\n"; - std::cout.flush(); - } - - - ~StandardOutput() override = default; - - }; - - enum class LoggerState - { - Endline, - Flush, - Exception, - Exit - }; - - class Logger - { - static Logger g_instance; - static darray g_sinks; - - protected: - - Severity p_severity; - std::ostringstream p_stream; - int p_exitcode; - sarray p_levelNames; - darray p_buffer; - - protected: - - Logger() : - p_severity {Emergency}, - p_stream {}, - p_exitcode {-1}, - p_levelNames { "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"} - {} - - static Logger& instance() - { - return g_instance; - } - - public: - - static void destroy() - { - for (Sink* sink : g_sinks) - delete sink; - g_sinks.clear(); - } - - static darray& sinks() - { - return g_sinks; - } - - static void addSink(Sink* sink) - { - if (sink != nullptr) - g_sinks.push(sink); - } - - static Severity severity() - { - return g_instance.p_severity; - } - - static void severity(Severity severity) - { - g_instance.p_severity = severity; - } - - // begin functions - friend - std::ostringstream&& log(Severity severity); - - - - // end functions - friend - LoggerState endl(); - - friend - LoggerState flush(); - - friend - LoggerState exception(); - - friend - LoggerState exit(int code); - - // - friend - std::ostream& operator<<(std::ostream& stream, const LoggerState& state); - }; - - // - Logger Logger::g_instance; - darray Logger::g_sinks; - - // - std::ostringstream&& log(Severity severity) - { - Logger& instance = Logger::instance(); - instance.p_severity = severity; - //std::ostringstream oss; - //return instance.p_stream; - return std::move(std::ostringstream());// oss;//std::forward(oss); - } - - std::ostringstream&& error() - { - return log(Error); - } - - // - LoggerState endl() - { - return LoggerState::Endline; - } - - LoggerState flush() - { - return LoggerState::Flush; - } - - LoggerState exception() - { - return LoggerState::Exception; - } - - LoggerState exit(int code) - { - Logger& instance = Logger::instance(); - instance.p_exitcode = code; - return LoggerState::Exit; - } - - - std::ostream& operator<<(std::ostream& stream, const LoggerState& state) - { - stream << std::endl; - std::ostringstream oss; - oss << stream.rdbuf(); - std::string test = oss.str(); - std::cout << test << std::endl; - //static std::mutex mtx; - //std::lock_guard lock(mtx); - Logger& instance = Logger::instance(); - - if (state >= LoggerState::Endline) - { - stream << "\n"; - } - - //instance.p_stream << stream.rdbuf(); - - - if (state >= LoggerState::Flush) - { - // default sink - if (Logger::sinks().is_empty()) - { - std::unique_ptr sink = std::make_unique(Logger::severity()); - sink->addMessage(oss.str()); - sink->flush(); - //delete sink; - } - - // global sinks - for (Sink* sink : Logger::sinks()) - { - sink->addMessage(oss.str()); - sink->flush(); - } - } - - if (state == LoggerState::Exception) - { - throw std::runtime_error(oss.str()); - } - - if (state == LoggerState::Exit) - { - std::exit(instance.p_exitcode); - } - - //instance.p_stream.flush(); - return stream; - } -} -} +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hpr::logging +{ + + enum Severity + { + Emergency, + Alert, + Critical, + Error, + Warning, + Notice, + Info, + Debug + }; + + class Sink + { + //friend class Logger; + + protected: + + Severity p_severity; + std::string p_message; + + public: + + Sink() : + p_severity {Emergency}, + p_message {} + {} + + Sink(Severity severity) : + p_severity {severity}, + p_message {} + {} + + void addMessage(const std::string& message) + { + p_message = message; + } + + virtual + void flush() = 0; + /*{ + p_message.clear(); + }*/ + + virtual + Sink* clone() const = 0; + + + virtual + ~Sink() = default; + + }; + + class StandardOutput : public Sink + { + + public: + + StandardOutput() : + Sink() + {} + + explicit + StandardOutput(Severity severity) : + Sink(severity) + {} + + void flush() override + { + if (p_severity <= Error) + std::cerr << p_message;// << "\n"; + if (p_severity > Error && p_severity <= Debug) + std::cout << p_message;// << "\n"; + std::cout.flush(); + //p_message.clear(); + } + + StandardOutput* clone() const override + { + return new StandardOutput(*this); + } + + ~StandardOutput() override = default; + + }; + + class FileOutput : public Sink + { + + protected: + + std::string p_filename; + static std::mutex mutex; + public: + + FileOutput() = delete; + + FileOutput(Severity severity, const std::string& filename) : + Sink {severity}, + p_filename {filename} + {} + + void flush() override + { + auto path = std::filesystem::canonical(std::filesystem::path(p_filename)); + std::ofstream sf {path, std::ios::app}; + + if (!sf.is_open()) + throw std::runtime_error("Failed to open file"); + else + { + std::lock_guard lock(mutex); + sf << p_message; + } + } + + FileOutput* clone() const override + { + return new FileOutput(*this); + } + + ~FileOutput() override = default; + + }; + + std::mutex FileOutput::mutex; + + class DumbassOutput : public Sink + { + + public: + + DumbassOutput() : + Sink() + {} + + explicit + DumbassOutput(Severity severity) : + Sink(severity) + {} + + void flush() override + { + std::cout << "BLUAARUARA";// << "\n"; + std::cout.flush(); + p_message.clear(); + } + + DumbassOutput* clone() const override + { + return new DumbassOutput(*this); + } + + ~DumbassOutput() override = default; + + }; + + + class Logger + { + + public: + + enum class State + { + Endline, + Flush, + Exception, + Exit + }; + + private: + + static std::mutex mutex; + + protected: + + Severity p_severity; + std::ostringstream p_stream; + int p_exitcode; + std::array p_levelNames; + std::vector p_sinks; + std::source_location p_location; + bool p_isGlobal; + std::string p_format; + + public: + + Logger() : + p_severity {Emergency}, + p_stream {"", std::ios::out | std::ios::ate }, + p_exitcode {-1}, + p_levelNames { "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"}, + p_sinks {}, + p_location {}, + p_isGlobal {false}, + p_format {"{levelname}({location}): {message}"} + {} + + explicit + Logger(Severity severity) : + p_severity {severity}, + p_stream {}, + p_exitcode {-1}, + p_levelNames { "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"}, + p_sinks {}, + p_location {}, + p_isGlobal {false}, + p_format {"{levelname}: {filename}:{location}: {message}"} + {} + + Logger(const Logger& logger) + { + copy(*this, logger); + } + + Logger& operator=(const Logger& logger) + { + copy(*this, logger); + return *this; + } + + virtual + ~Logger() + { + for (auto& sink : p_sinks) + delete sink; + } + + inline + void severity(Severity severity) + { + p_severity = severity; + } + + inline + Severity severity() + { + return p_severity; + } + + inline + std::string stream() + { + return p_stream.str(); + } + + inline + void levelname(Severity severity, const std::string& name) + { + p_levelNames[severity] = name; + } + + inline + std::string levelname(Severity severity) + { + return p_levelNames[severity]; + } + + inline + void location(const std::source_location& location) + { + p_location = location; + } + + inline + std::source_location location() + { + return p_location; + } + + inline + void format(const std::string& format) + { + p_format = format; + } + + inline + std::string format() + { + return p_format; + } + + inline + const std::vector& sinks() + { + return p_sinks; + } + + inline + void addSink(Sink* sink) + { + if (sink != nullptr) + { + p_sinks.push_back(sink->clone()); + //p_sinks.push_back(new T()); + //*p_sinks.back() = *sink; + //p_sinks.emplace_back(); + //p_sinks.back() = reinterpret_cast(&sink); + } + } + + inline + void removeSink(std::size_t n) + { + p_sinks.erase(p_sinks.begin() + n); + } + + inline + Sink* defaultSink() + { + return new StandardOutput(p_severity); + } + + std::string formatMessage(const std::string& message) + { + auto replace = [](std::string& lhs, const std::string& rep, const std::string& replacement) + { + std::size_t index = 0; + while ((index = lhs.find(rep, index)) != std::string::npos) + { + lhs.replace(index, rep.length(), replacement); + index += replacement.length(); + } + return lhs; + }; + + std::string target = p_format; + target = replace(target, "{levelname}", p_levelNames[p_severity]); + target = replace(target, "{location}", std::to_string(p_location.line())); + target = replace(target, "{function}", p_location.function_name()); + target = replace(target, "{filename}", p_location.file_name()); + target = replace(target, "{message}", message); + + return target; + } + + template + Logger& operator<<(const T& message) + { + if constexpr (std::is_same_v) + { + //std::lock_guard lock(mutex); + + if (message >= State::Endline) + { + p_stream << "\n"; + } + + if (message >= State::Flush) + { + std::string formattedMessage = formatMessage(p_stream.str()); + + if (p_sinks.empty()) + { + std::cout << "default sink" << std::endl; + Sink* sink = defaultSink(); + sink->addMessage(formattedMessage); + sink->flush(); + delete sink; + } + else + { + std::cout << "local sinks" << std::endl; + for (auto& sink : p_sinks) + { + if (sink != nullptr) + { + sink->addMessage(formattedMessage); + sink->flush(); + } + } + } + + p_stream.flush(); + } + + if (message == State::Exception) + { + throw std::runtime_error(p_stream.str()); + } + + if (message == State::Exit) + { + std::exit(p_exitcode); + } + + } + else + { + std::lock_guard lock(mutex); + p_stream << message; + } + return *this; + } + + + friend inline + Logger log(Severity severity, std::source_location location); + + public: + + static void copy(Logger& lhs, const Logger& rhs) + { + lhs.p_severity = rhs.p_severity; + + lhs.p_stream.flush(); + lhs.p_stream << rhs.p_stream.str(); + + lhs.p_exitcode = rhs.p_exitcode; + lhs.p_levelNames = rhs.p_levelNames; + lhs.p_isGlobal = false; + lhs.p_location = rhs.p_location; + lhs.p_format = rhs.p_format; + + if (!rhs.p_sinks.empty()) + { + lhs.p_sinks.reserve(rhs.p_sinks.size()); + for (const auto& sink : rhs.p_sinks) + { + lhs.p_sinks.push_back(sink->clone()); + //*lhs.p_sinks.back() = *sink; + //lhs.p_sinks.push_back(std::unique_ptr(sink)); + } + } + } + + private: + + static std::shared_ptr globalLogger; + + public: + + friend inline + void create(); + + friend inline + void destroy(); + + friend inline + std::shared_ptr& get(); + }; + + std::shared_ptr Logger::globalLogger; + std::mutex Logger::mutex; + + inline + Logger::State flush() + { + return Logger::State::Flush; + } + + inline + Logger log(Severity severity, std::source_location location = std::source_location::current()) + { + if (Logger::globalLogger != nullptr) + { + Logger::globalLogger->severity(severity); + Logger logger = *Logger::globalLogger; + logger.location(location); + return logger; + } + else + { + Logger logger = Logger(severity); + logger.location(location); + return logger; + } + } + + inline + void create() + { + Logger::globalLogger = std::make_shared(); + Logger::globalLogger->p_isGlobal = true; + } + + inline + void destroy() + { + if (Logger::globalLogger != nullptr) + Logger::globalLogger.reset(); + } + + inline + std::shared_ptr& get() + { + return Logger::globalLogger; + } + + + inline + Logger emergency(std::source_location location = std::source_location::current()) + { + return log(Emergency, location); + } + + inline + Logger alert(std::source_location location = std::source_location::current()) + { + return log(Alert, location); + } + + inline + Logger critical(std::source_location location = std::source_location::current()) + { + return log(Critical, location); + } + + inline + Logger error(std::source_location location = std::source_location::current()) + { + return log(Error, location); + } + + inline + Logger warning(std::source_location location = std::source_location::current()) + { + return log(Warning, location); + } + + inline + Logger notice(std::source_location location = std::source_location::current()) + { + return log(Notice, location); + } + + inline + Logger info(std::source_location location = std::source_location::current()) + { + return log(Info, location); + } + + inline + Logger debug(std::source_location location = std::source_location::current()) + { + return log(Debug, location); + } + +} \ No newline at end of file diff --git a/source/hpr/io/tests/CMakeLists.txt b/source/hpr/io/tests/CMakeLists.txt deleted file mode 100644 index 1644c07..0000000 --- a/source/hpr/io/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -file(GLOB tests_cpp "*.cpp") - -add_executable(${PROJECT_NAME}-tests - ${tests_cpp} -) - -target_link_libraries(${PROJECT_NAME}-tests - PUBLIC - hpr::${PROJECT_NAME} - PRIVATE - GTest::gtest_main -) - -gtest_add_tests(TARGET ${PROJECT_NAME}-tests) diff --git a/source/hpr/io/tests/io-test.cpp b/source/hpr/io/tests/io-test.cpp index ed615a5..4e6149f 100644 --- a/source/hpr/io/tests/io-test.cpp +++ b/source/hpr/io/tests/io-test.cpp @@ -1,8 +1,9 @@ #include -#include "../logger.hpp" +#include #include + void task1() { using namespace hpr; diff --git a/source/hpr/math.hpp b/source/hpr/math.hpp index bf29022..131be38 100644 --- a/source/hpr/math.hpp +++ b/source/hpr/math.hpp @@ -1,5 +1,6 @@ -#pragma once - -#include "math/scalar.hpp" -#include "math/vector.hpp" -#include "math/matrix.hpp" \ No newline at end of file +#pragma once + +#include +#include +#include +#include diff --git a/source/hpr/math/CMakeLists.txt b/source/hpr/math/CMakeLists.txt index 781214f..b62cdb4 100644 --- a/source/hpr/math/CMakeLists.txt +++ b/source/hpr/math/CMakeLists.txt @@ -1,62 +1,25 @@ cmake_minimum_required(VERSION 3.16) project(math - VERSION "${HPR_PROJECT_VERSION}" + VERSION "${HPR_VERSION}" LANGUAGES CXX ) -add_library(${PROJECT_NAME} INTERFACE) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) +hpr_add_library(${PROJECT_NAME} INTERFACE) -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../math.hpp" "*.hpp" "scalar/*.hpp" "vector/*.hpp" "matrix/*.hpp" "quaternion/*.hpp" +hpr_collect_interface(${PROJECT_NAME} + "../math.hpp" + "*.hpp" + "scalar/*.hpp" + "vector/*.hpp" + "matrix/*.hpp" + "quaternion/*.hpp" ) -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ -) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> ) -if(HPR_TEST) - add_subdirectory(tests) -endif() \ No newline at end of file +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) +hpr_tests(${PROJECT_NAME} tests) diff --git a/source/hpr/math/integer.hpp b/source/hpr/math/integer.hpp index 28c1742..7bea965 100644 --- a/source/hpr/math/integer.hpp +++ b/source/hpr/math/integer.hpp @@ -1,4 +1,4 @@ -#pragma once - -#include "integer/integer.hpp" -#include "integer/size.hpp" +#pragma once + +#include +#include diff --git a/source/hpr/math/integer/integer.hpp b/source/hpr/math/integer/integer.hpp index 270559a..3512c36 100644 --- a/source/hpr/math/integer/integer.hpp +++ b/source/hpr/math/integer/integer.hpp @@ -1,17 +1,17 @@ -#pragma once - -#include - - -namespace hpr -{ - // type traits - - template - struct is_integer : public std::is_integral {}; - - // concepts - - template - concept IsInteger = is_integer::value; +#pragma once + +#include + + +namespace hpr +{ + // type traits + + template + struct is_integer : public std::is_integral {}; + + // concepts + + template + concept IsInteger = is_integer::value; } \ No newline at end of file diff --git a/source/hpr/math/integer/size.hpp b/source/hpr/math/integer/size.hpp index ca5e851..92a8617 100644 --- a/source/hpr/math/integer/size.hpp +++ b/source/hpr/math/integer/size.hpp @@ -1,22 +1,22 @@ -#pragma once - -#include - -namespace hpr -{ - - using Size = std::size_t; - - // type traits - - template - struct is_size : public std::integral_constant::value && std::is_unsigned::value> {}; - - // concepts - - template - concept IsSize = is_size::value || std::convertible_to; - - - //using Size = typename IsSize::type; +#pragma once + +#include + +namespace hpr +{ + + using Size = std::size_t; + + // type traits + + template + struct is_size : public std::integral_constant::value && std::is_unsigned::value> {}; + + // concepts + + template + concept IsSize = is_size::value || std::convertible_to; + + + //using Size = typename IsSize::type; } \ No newline at end of file diff --git a/source/hpr/math/matrix.hpp b/source/hpr/math/matrix.hpp index a23fe30..c00e1c4 100644 --- a/source/hpr/math/matrix.hpp +++ b/source/hpr/math/matrix.hpp @@ -1,5 +1,5 @@ #pragma once -#include "matrix/matrix_space.hpp" -#include "matrix/transform.hpp" -#include "matrix/clip_space.hpp" +#include +#include +#include diff --git a/source/hpr/math/matrix/clip_space.hpp b/source/hpr/math/matrix/clip_space.hpp index 9b3b15e..35ea754 100644 --- a/source/hpr/math/matrix/clip_space.hpp +++ b/source/hpr/math/matrix/clip_space.hpp @@ -1,54 +1,52 @@ -#pragma once - -#include "../matrix.hpp" - - -namespace hpr -{ - -template -inline -Matrix ortho(T left, T right, T bottom, T top) -{ - Matrix ms; - ms.fill(1); - ms(0, 0) = 2 / (right - left); - ms(1, 1) = 2 / (top - bottom); - ms(2, 2) = -1; - ms(3, 0) = -(right + left) / (right - left); - ms(3, 1) = -(top + bottom) / (top - bottom); - return ms; -} - -template -inline -Matrix ortho(T left, T right, T bottom, T top, T zNear, T zFar) -{ - Matrix ms {1}; - //ms.fill(1); - ms(0, 0) = 2 / (right - left); - ms(1, 1) = 2 / (top - bottom); - ms(2, 2) = 2 / (zFar - zNear); - ms(3, 0) = -(right + left) / (right - left); - ms(3, 1) = -(top + bottom) / (top - bottom); - ms(3, 2) = -(zFar + zNear) / (zFar - zNear); - return ms; -} - -template -inline -Matrix perspective(T fovy, T aspect, T zNear, T zFar) -{ - assert(abs(aspect - std::numeric_limits::epsilon()) > 0); - Matrix ms; - const T halfFovyTan = tan(fovy / 2); - ms(0, 0) = 1 / (aspect * halfFovyTan); - ms(1, 1) = 1 / halfFovyTan; - ms(2, 2) = (zFar + zNear) / (zFar - zNear); - ms(2, 3) = 1; - ms(3, 2) = -(2 * zFar * zNear) / (zFar - zNear); - return ms; -} - - -} +#pragma once + +#include + + +namespace hpr +{ + +template +inline +Matrix ortho(T left, T right, T bottom, T top) +{ + Matrix ms = Matrix::identity(); + ms(0, 0) = static_cast(2) / (right - left); + ms(1, 1) = static_cast(2) / (top - bottom); + ms(2, 2) = -static_cast(1); + ms(3, 0) = -(right + left) / (right - left); + ms(3, 1) = -(top + bottom) / (top - bottom); + return ms; +} + +template +inline +Matrix ortho(T left, T right, T bottom, T top, T zNear, T zFar) +{ + Matrix ms = Matrix::identity(); + ms(0, 0) = static_cast(2) / (right - left); + ms(1, 1) = static_cast(2) / (top - bottom); + ms(2, 2) = -static_cast(2) / (zFar - zNear); + ms(0, 3) = -(right + left) / (right - left); + ms(1, 3) = -(top + bottom) / (top - bottom); + ms(2, 3) = -(zFar + zNear) / (zFar - zNear); + return ms; +} + +template +inline +Matrix perspective(T fovy, T aspect, T zNear, T zFar) +{ + assert(abs(aspect - std::numeric_limits::epsilon()) > 0); + Matrix ms; + const T halfFovyTan = tan(fovy / 2); + ms(0, 0) = static_cast(1) / (aspect * halfFovyTan); + ms(1, 1) = static_cast(1) / halfFovyTan; + ms(2, 2) = -(zFar + zNear) / (zFar - zNear); + ms(3, 2) = -static_cast(1); + ms(2, 3) = -(static_cast(2) * zFar * zNear) / (zFar - zNear); + return ms; +} + + +} diff --git a/source/hpr/math/matrix/matrix_space.hpp b/source/hpr/math/matrix/matrix.hpp similarity index 81% rename from source/hpr/math/matrix/matrix_space.hpp rename to source/hpr/math/matrix/matrix.hpp index f14167c..0e1c187 100644 --- a/source/hpr/math/matrix/matrix_space.hpp +++ b/source/hpr/math/matrix/matrix.hpp @@ -1,364 +1,392 @@ -#pragma once - -#include "../integer.hpp" -#include "../scalar.hpp" -#include "../../containers/array/static_array.hpp" - - -namespace hpr -{ - -// forward declarations - -template requires (Rows >= 0 && Cols >= 0) -class Matrix; - -template -using SubMatrix = typename std::conditional<(Rows >= 2 && Cols >= 2), Matrix, Matrix>::type; - -// type traits - -template -struct is_matrix : public std::false_type {}; - -template -struct is_matrix> : public std::true_type {}; - -// concepts - -template -concept IsMatrix = is_matrix::value; - -} - -namespace hpr -{ - -template requires (Rows >= 0 && Cols >= 0) -class Matrix : public StaticArray -{ - - using base = StaticArray; - -public: - - using value_type = Type; - using size_type = Size; - using pointer = Type*; - using reference = Type&; - using iterator = Iterator; - using const_reference = Type const&; - using const_iterator = Iterator; - -protected: - - size_type p_rows; - size_type p_cols; - -public: - - inline - Matrix() : - base {}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(const Matrix& ms) : - base {static_cast(ms)}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(Matrix&& ms) noexcept: - base {std::forward(static_cast(ms))}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix& operator=(const Matrix& ms) - { - base::operator=(ms); - return *this; - } - - inline explicit - Matrix(const base& vs) : - base {vs}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline explicit - Matrix(base&& vs) noexcept: - base {std::forward(vs)}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(typename base::iterator start, typename base::iterator end) : - base {start, end}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(typename base::const_iterator start, typename base::const_iterator end) : - base {start, end}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(std::initializer_list list) : - base {list}, - p_rows {Rows}, - p_cols {Cols} - {} - - template - inline - Matrix(value_type&& v, Args&& ...args) requires (1 + sizeof...(args) == Rows * Cols): - base {v, static_cast(std::forward(args))...}, - p_rows {Rows}, - p_cols {Cols} - {} - - inline - Matrix(const value_type& v) : - base {}, - p_rows {Rows}, - p_cols {Cols} - { - for (Size n = 0; n < Rows * Cols; ++n) - (*this)[n] = v; - } - - inline - Matrix& operator=(const value_type& v) - { - for (Size n = 0; n < Rows * Cols; ++n) - (*this)[n] = v; - return *this; - } - - // access - - inline - reference operator()(size_type row, size_type col) - { - if (row >= p_rows || std::numeric_limits::max() - p_rows < row) - throw std::out_of_range("Row index is out of range"); - if (col >= p_cols || std::numeric_limits::max() - p_cols < col) - throw std::out_of_range("Column index is out of range"); - return (*this)[col + p_rows * row]; - } - - inline - const_reference operator()(size_type row, size_type col) const - { - if (row >= p_rows || std::numeric_limits::max() - p_rows < row) - throw std::out_of_range("Row index is out of range"); - if (col >= p_cols || std::numeric_limits::max() - p_cols < col) - throw std::out_of_range("Column index is out of range"); - return (*this)[col + p_rows * row]; - } - - Vector row(size_type row) - { - Vector vs; - for (auto n = 0; n < Cols; ++n) - vs[n] = (*this)(row, n); - return vs; - } - - Vector row(size_type row) const - { - Vector vs; - for (auto n = 0; n < Cols; ++n) - vs[n] = (*this)(row, n); - return vs; - } - - void row(size_type row, const Vector& vs) - { - for (auto n = 0; n < Cols; ++n) - (*this)(n, row) = vs[n]; - } - - Vector col(size_type col) - { - Vector vs; - for (auto n = 0; n < Rows; ++n) - vs[n] = (*this)(n, col); - return vs; - } - - void col(size_type col, const Vector& vs) - { - for (auto n = 0; n < Rows; ++n) - (*this)(n, col) = vs[n]; - } - - [[nodiscard]] constexpr size_type rows() const { return p_rows; } - [[nodiscard]] constexpr size_type cols() const { return p_cols; } - - // member functions - - [[nodiscard]] - constexpr - bool is_square() const - { - return p_rows == p_cols; - } - - inline - Matrix& fill(value_type value) - { - for (auto n = 0; n < this->size(); ++n) - (*this)[n] = value; - return *this; - } - - // Global functions - - static inline - Matrix identity() - { - Matrix ms; - for (auto n = 0; n < Rows; ++n) - for (auto k = 0; k < Cols; ++k) - ms(n, k) = 1; - return ms; - } -}; - -// global operators - -template inline Matrix operator+(const Matrix& lhs) { Matrix ms; for (Size n = 0; n < lhs.size(); ++n) ms[n] = lhs[n]; return ms; } -template inline Matrix operator-(const Matrix& lhs) { Matrix ms; for (Size n = 0; n < lhs.size(); ++n) ms[n] = -lhs[n]; return ms; } - -template inline Matrix& operator+=(Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] += rhs[n]; return lhs; } -template inline Matrix& operator-=(Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] -= rhs[n]; return lhs; } -template requires (R == C2 && R2 == C) inline Matrix& operator*=(Matrix& lhs, const Matrix& rhs) { Matrix temp {lhs}; for (Size n = 0; n < R; ++n) for (Size k = 0; k < C; ++k) lhs(n, k) = sum(temp.col(k) * rhs.row(n)); return lhs; } - -template inline Matrix operator+(const Matrix& lhs, const Matrix& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] += rhs[n]; return ms; } -template inline Matrix operator-(const Matrix& lhs, const Matrix& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] -= rhs[n]; return ms; } -template requires (R == C2 && R2 == C) inline Matrix operator*(const Matrix& lhs, const Matrix& rhs) { Matrix ms; for (Size n = 0; n < R; ++n) for (Size k = 0; k < C; ++k) ms(n, k) = sum(lhs.col(k) * rhs.row(n)); return ms; } - -template inline bool operator==(const Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) if (lhs[n] != rhs[n]) return false; return true; } -template inline bool operator!=(const Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) if (lhs[n] == rhs[n]) return false; return true; } - - -template inline Matrix& operator+=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] += rhs; return lhs; } -template inline Matrix& operator-=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] -= rhs; return lhs; } -template inline Matrix& operator*=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] *= rhs; return lhs; } -template inline Matrix& operator/=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] /= rhs; return lhs; } - -template inline Matrix operator+(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] += rhs; return ms; } -template inline Matrix operator-(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] -= rhs; return ms; } -template inline Matrix operator*(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] *= rhs; return ms; } -template inline Matrix operator/(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] /= rhs; return ms; } - - -template inline Vector operator*(const Matrix& ms, const Vector& vs) { Vector res; for (Size n = 0; n < R; ++n) res[0] = sum(ms.row(n) * vs); return res; } -template inline Vector operator*(const Vector& vs, const Matrix& ms) { Vector res; for (Size n = 0; n < C; ++n) res[0] = sum(ms.col(n) * vs); return res; } - -template inline bool operator==(const Matrix& lhs, const Vector& rhs) { return false; } -template inline bool operator!=(const Matrix& lhs, const Vector& rhs) { return true; } - -// matrix operations - -//! Transpose matrix -template -inline -Matrix transpose(const Matrix& ms) -{ - Matrix res; - for (Size n = 0; n < R; ++n) - for (Size k = 0; k < C; ++k) - res(k, n) = ms(n, k); - return res; -} - -//! Trace of a matrix -template -inline -T trace(const Matrix& ms) requires (R == C) -{ - T res; - for (auto n = 0; n < R; ++n) - res += ms(n, n); - return res; -} - -//! Minor of a matrix -template -inline -SubMatrix minor(const Matrix& ms, Size row, Size col) -{ - if (ms.size() < 4) - throw std::runtime_error("Matrix should be greater 2x2"); - - SubMatrix minor; - auto minor_iter = minor.begin(); - for (auto n = 0; n < R; ++n) - for (auto k = 0; k < C; ++k) - if (k != col && n != row) - *(minor_iter++) = ms[k + ms.rows() * n]; - return minor; -} - -//! Determinant of a matrix -template -inline -scalar det(const Matrix& ms) requires (R == C) -{ - if (ms.size() == 1) - return ms[0]; - - else if (ms.size() == 4) - return ms(0, 0) * ms(1, 1) - ms(0, 1) * ms(1, 0); - - else { - scalar res = 0; - for (auto n = 0; n < ms.cols(); ++n) - res += pow(-1, n) * ms(0, n) * det(minor(ms, 0, n)); - return res; - } -} - -//! Adjoint matrix -template -inline -Matrix adj(const Matrix& ms) -{ - Matrix res; - for (auto n = 0; n < R; ++n) - for (auto k = 0; k < C; ++k) - res(n, k) = pow(-1, n + k) * det(minor(ms, n, k)); - return transpose(res); -} - -//! Inverse matrix -template -inline -Matrix inv(const Matrix& ms) -{ - return adj(ms) / det(ms); -} - -// Aliases - -template -using mat = Matrix; - -using mat2 = Matrix; -using mat3 = Matrix; -using mat4 = Matrix; - +#pragma once + +#include +#include +#include +#include + + +namespace hpr +{ + +// forward declarations + +template requires (Rows >= 0 && Cols >= 0) +class Matrix; + +template +using SubMatrix = typename std::conditional<(Rows >= 2 && Cols >= 2), Matrix, Matrix>::type; + +// type traits + +template +struct is_matrix : public std::false_type {}; + +template +struct is_matrix> : public std::true_type {}; + +// concepts + +template +concept IsMatrix = is_matrix::value; + +} + +namespace hpr +{ + +template requires (Rows >= 0 && Cols >= 0) +class Matrix : public StaticArray +{ + + using base = StaticArray; + +public: + + using value_type = Type; + using size_type = Size; + using pointer = Type*; + using reference = Type&; + using iterator = Iterator; + using const_reference = Type const&; + using const_iterator = Iterator; + +protected: + + size_type p_rows; + size_type p_cols; + +public: + + friend constexpr + void swap(Matrix& main, Matrix& other) + { + using std::swap; + swap(static_cast(main), static_cast(other)); + swap(main.p_rows, other.p_rows); + swap(main.p_cols, other.p_cols); + } + + inline + Matrix() : + base {}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(const Matrix& ms) : + base {static_cast(ms)}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(Matrix&& ms) noexcept: + base {std::forward(static_cast(ms))}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix& operator=(const Matrix& ms) + { + //base::operator=(ms); + swap(*this, ms); + return *this; + } + + inline explicit + Matrix(const base& vs) : + base {vs}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline explicit + Matrix(base&& vs) noexcept: + base {std::forward(vs)}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(typename base::iterator start, typename base::iterator end) : + base {start, end}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(typename base::const_iterator start, typename base::const_iterator end) : + base {start, end}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(std::initializer_list list) : + base {list}, + p_rows {Rows}, + p_cols {Cols} + {} + + template + inline + Matrix(value_type&& v, Args&& ...args) requires (1 + sizeof...(args) == Rows * Cols): + base {v, static_cast(std::forward(args))...}, + p_rows {Rows}, + p_cols {Cols} + {} + + inline + Matrix(const value_type& v) : + base {}, + p_rows {Rows}, + p_cols {Cols} + { + for (Size n = 0; n < Rows * Cols; ++n) + (*this)[n] = v; + } + + inline + Matrix& operator=(const value_type& v) + { + for (Size n = 0; n < Rows * Cols; ++n) + (*this)[n] = v; + return *this; + } + + inline explicit + Matrix(const Quaternion& q) requires (Rows == 3 && Cols == 3 || Rows == 4 && Cols == 4) : + base {}, + p_rows {Rows}, + p_cols {Cols} + { + const scalar s = pow(norm(q), -2); + + (*this)(0, 0) = 1 - 2 * s * (q[2] * q[2] + q[3] * q[3]); + (*this)(1, 0) = 2 * s * (q[1] * q[2] - q[3] * q[0]); + (*this)(2, 0) = 2 * s * (q[1] * q[3] - q[2] * q[0]); + + (*this)(0, 1) = 2 * s * (q[1] * q[2] + q[3] * q[0]); + (*this)(1, 1) = 1 - 2 * s * (q[1] * q[1] + q[3] * q[3]); + (*this)(2, 1) = 2 * s * (q[2] * q[3] + q[1] * q[0]); + + (*this)(0, 2) = 2 * s * (q[1] * q[3] + q[2] * q[0]); + (*this)(1, 2) = 2 * s * (q[2] * q[3] + q[1] * q[0]); + (*this)(2, 2) = 1 - 2 * s * (q[1] * q[1] + q[2] * q[2]); + + if constexpr (Rows == 4) + (*this)(3, 3) = 1; + } + + + // access + + inline + reference operator()(size_type row, size_type col) + { + if (row >= p_rows || std::numeric_limits::max() - p_rows < row) + throw std::out_of_range("Row index is out of range"); + if (col >= p_cols || std::numeric_limits::max() - p_cols < col) + throw std::out_of_range("Column index is out of range"); + return (*this)[col + p_rows * row]; + } + + inline + const_reference operator()(size_type row, size_type col) const + { + if (row >= p_rows || std::numeric_limits::max() - p_rows < row) + throw std::out_of_range("Row index is out of range"); + if (col >= p_cols || std::numeric_limits::max() - p_cols < col) + throw std::out_of_range("Column index is out of range"); + return (*this)[col + p_rows * row]; + } + + Vector row(size_type row) const + { + Vector vs; + for (auto n = 0; n < Cols; ++n) + vs[n] = (*this)(row, n); + return vs; + } + + void row(size_type row, const Vector& vs) + { + for (auto n = 0; n < Cols; ++n) + (*this)(row, n) = vs[n]; + } + + Vector col(size_type col) const + { + Vector vs; + for (auto n = 0; n < Rows; ++n) + vs[n] = (*this)(n, col); + return vs; + } + + void col(size_type col, const Vector& vs) + { + for (auto n = 0; n < Rows; ++n) + (*this)(n, col) = vs[n]; + } + + [[nodiscard]] constexpr size_type rows() const { return p_rows; } + [[nodiscard]] constexpr size_type cols() const { return p_cols; } + + // member functions + + [[nodiscard]] + constexpr + bool is_square() const + { + return p_rows == p_cols; + } + + inline + Matrix& fill(value_type value) + { + for (auto n = 0; n < this->size(); ++n) + (*this)[n] = value; + return *this; + } + + // Global functions + + static inline + Matrix identity() + { + Matrix ms; + for (auto n = 0; n < Rows; ++n) + //for (auto k = 0; k < Cols; ++k) + ms(n, n) = 1; + return ms; + } +}; + +// global operators + +template inline Matrix operator+(const Matrix& lhs) { Matrix ms; for (Size n = 0; n < lhs.size(); ++n) ms[n] = lhs[n]; return ms; } +template inline Matrix operator-(const Matrix& lhs) { Matrix ms; for (Size n = 0; n < lhs.size(); ++n) ms[n] = -lhs[n]; return ms; } + +template inline Matrix& operator+=(Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] += rhs[n]; return lhs; } +template inline Matrix& operator-=(Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] -= rhs[n]; return lhs; } +template inline Matrix& operator*=(Matrix& lhs, const Matrix& rhs) { Matrix temp {lhs}; for (Size n = 0; n < R; ++n) for (Size k = 0; k < C; ++k) lhs(n, k) = sum(temp.col(k) * rhs.row(n)); return lhs; } + +template inline Matrix operator+(const Matrix& lhs, const Matrix& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] += rhs[n]; return ms; } +template inline Matrix operator-(const Matrix& lhs, const Matrix& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] -= rhs[n]; return ms; } +template inline Matrix operator*(const Matrix& lhs, const Matrix& rhs) { Matrix ms; for (Size n = 0; n < R; ++n) for (Size k = 0; k < C; ++k) ms(n, k) = sum(lhs.col(k) * rhs.row(n)); return ms; } + +template inline bool operator==(const Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) if (lhs[n] != rhs[n]) return false; return true; } +template inline bool operator!=(const Matrix& lhs, const Matrix& rhs) { for (Size n = 0; n < lhs.size(); ++n) if (lhs[n] == rhs[n]) return false; return true; } + + +template inline Matrix& operator+=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] += rhs; return lhs; } +template inline Matrix& operator-=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] -= rhs; return lhs; } +template inline Matrix& operator*=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] *= rhs; return lhs; } +template inline Matrix& operator/=(Matrix& lhs, const T& rhs) { for (Size n = 0; n < lhs.size(); ++n) lhs[n] /= rhs; return lhs; } + +template inline Matrix operator+(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] += rhs; return ms; } +template inline Matrix operator-(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] -= rhs; return ms; } +template inline Matrix operator*(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] *= rhs; return ms; } +template inline Matrix operator/(const Matrix& lhs, const T& rhs) { Matrix ms {lhs}; for (Size n = 0; n < lhs.size(); ++n) ms[n] /= rhs; return ms; } + + +template inline Vector operator*(const Matrix& ms, const Vector& vs) { Vector res; for (Size n = 0; n < R; ++n) res[n] = sum(ms.row(n) * vs); return res; } +template inline Vector operator*(const Vector& vs, const Matrix& ms) { Vector res; for (Size n = 0; n < C; ++n) res[n] = sum(ms.col(n) * vs); return res; } + +template inline bool operator==(const Matrix& lhs, const Vector& rhs) { return false; } +template inline bool operator!=(const Matrix& lhs, const Vector& rhs) { return true; } + +// matrix operations + +//! Transpose matrix +template +inline +Matrix transpose(const Matrix& ms) +{ + Matrix res; + for (Size n = 0; n < R; ++n) + for (Size k = 0; k < C; ++k) + res(k, n) = ms(n, k); + return res; +} + +//! Trace of a matrix +template +inline +T trace(const Matrix& ms) requires (R == C) +{ + T res; + for (auto n = 0; n < R; ++n) + res += ms(n, n); + return res; +} + +//! Minor of a matrix +template +inline +SubMatrix minor(const Matrix& ms, Size row, Size col) +{ + if (ms.size() < 4) + throw std::runtime_error("Matrix should be greater 2x2"); + + SubMatrix minor; + auto minor_iter = minor.begin(); + for (auto n = 0; n < R; ++n) + for (auto k = 0; k < C; ++k) + if (k != col && n != row) + *(minor_iter++) = ms[k + ms.rows() * n]; + return minor; +} + +//! Determinant of a matrix +template +inline +scalar det(const Matrix& ms) requires (R == C) +{ + if (ms.size() == 1) + return ms[0]; + + else if (ms.size() == 4) + return ms(0, 0) * ms(1, 1) - ms(0, 1) * ms(1, 0); + + else { + scalar res = 0; + for (auto n = 0; n < ms.cols(); ++n) + res += pow(-1, n) * ms(0, n) * det(minor(ms, 0, n)); + return res; + } +} + +//! Adjoint matrix +template +inline +Matrix adj(const Matrix& ms) +{ + Matrix res; + for (auto n = 0; n < R; ++n) + for (auto k = 0; k < C; ++k) + res(n, k) = pow(-1, n + k) * det(minor(ms, n, k)); + return transpose(res); +} + +//! Inverse matrix +template +inline +Matrix inv(const Matrix& ms) +{ + return adj(ms) / det(ms); +} + +// Aliases + +template +using mat = Matrix; + +using mat2 = Matrix; +using mat3 = Matrix; +using mat4 = Matrix; + } \ No newline at end of file diff --git a/source/hpr/math/matrix/transform.hpp b/source/hpr/math/matrix/transform.hpp index 9836ddd..665ae3e 100644 --- a/source/hpr/math/matrix/transform.hpp +++ b/source/hpr/math/matrix/transform.hpp @@ -1,101 +1,90 @@ -#pragma once - -#include "../matrix.hpp" - - -namespace hpr -{ - -template -inline -Matrix translate(const Matrix& ms, const Vector& vs) -{ - Matrix res {ms}; - res.col(3, ms.row(0) * vs[0] + ms.row(1) * vs[1] + ms.row(2) * vs[2] + ms.row(3)); - - return res; -} - -template -inline -Vector translate(const Vector& vs1, const Vector& vs2) -{ - Matrix res = Matrix::identity(); - res.row(3, Vector(vs1, 0.)); - res = translate(res, vs2); - - return Vector(res.row(3)); -} - -template -inline -Matrix rotate(const Matrix& ms, const Vector& vs, T angle) -{ - const T cosv = cos(angle); - const T sinv = sin(angle); - Vector axis {normalize(vs)}; - Vector temp {(1. - cosv) * axis}; - - Matrix rot; - rot(0, 0) = cosv + temp[0] * axis[0]; - rot(0, 1) = temp[0] * axis[1] + sinv * axis[2]; - rot(0, 2) = temp[0] * axis[2] - sinv * axis[1]; - rot(1, 0) = temp[1] * axis[0] - sinv * axis[2]; - rot(1, 1) = cosv + temp[1] * axis[1]; - rot(1, 2) = temp[1] * axis[2] + sinv * axis[0]; - rot(2, 0) = temp[2] * axis[0] + sinv * axis[1]; - rot(2, 1) = temp[2] * axis[1] - sinv * axis[0]; - rot(2, 2) = cosv + temp[2] * axis[2]; - - Matrix res {ms}; - res.row(0, ms.row(0) * rot(0, 0) + ms.row(1) * rot(0, 1) + ms.row(2) * rot(0, 2)); - res.row(1, ms.row(0) * rot(1, 0) + ms.row(1) * rot(1, 1) + ms.row(2) * rot(1, 2)); - res.row(2, ms.row(0) * rot(2, 0) + ms.row(1) * rot(2, 1) + ms.row(2) * rot(2, 2)); - res.row(3, ms.row(3)); - - return res; -} - -template -inline -Vector rotate(const Vector& vs1, const Vector& vs2, T angle) -{ - Matrix res = Matrix::identity(); - res.row(3, Vector(vs1, 0.)); - res = rotate(res, vs2, angle); - - return Vector(res.row(3)); -} - -template -inline -Matrix scale(const Matrix& ms, const Vector& vs) -{ - Matrix res; - res.row(0, ms.row(0) * vs[0]); - res.row(1, ms.row(1) * vs[1]); - res.row(2, ms.row(2) * vs[2]); - res.row(3, ms.row(3)); - - return res; -} - - -template -inline -Matrix lookAt(const Matrix& ms, const Vector& eye, const Vector& center, const Vector& up) -{ - const Vector forward {normalize(center - eye)}; - const Vector left {normalize(cross(up, forward))}; - const Vector nup {cross(forward, left)}; - - Matrix res; - res.col(0, Vector(left, 0)); - res.col(1, Vector(nup, 0)); - res.col(2, Vector(forward, 0)); - res.row(3, -Vector(dot(left, eye), dot(nup, eye), dot(forward, eye), -1.)); - - return res; -} - -} +#pragma once + +#include +#include + + + +namespace hpr +{ + +template +inline +Matrix translate(const Matrix& ms, const Vector& vs) +{ + Matrix res {ms}; + res.col(3, ms.row(0) * vs[0] + ms.row(1) * vs[1] + ms.row(2) * vs[2] + ms.row(3)); + + return res; +} + +template +inline +Matrix rotate(const Matrix& ms, const Vector& vs, T angle) +{ + const T cosv = cos(angle); + const T sinv = sin(angle); + Vector axis {normalize(vs)}; + Vector temp {(static_cast(1) - cosv) * axis}; + + Matrix rot; + rot(0, 0) = cosv + temp[0] * axis[0]; + rot(0, 1) = temp[0] * axis[1] + sinv * axis[2]; + rot(0, 2) = temp[0] * axis[2] - sinv * axis[1]; + rot(1, 0) = temp[1] * axis[0] - sinv * axis[2]; + rot(1, 1) = cosv + temp[1] * axis[1]; + rot(1, 2) = temp[1] * axis[2] + sinv * axis[0]; + rot(2, 0) = temp[2] * axis[0] + sinv * axis[1]; + rot(2, 1) = temp[2] * axis[1] - sinv * axis[0]; + rot(2, 2) = cosv + temp[2] * axis[2]; + + Matrix res {ms}; + res.row(0, ms.row(0) * rot(0, 0) + ms.row(1) * rot(0, 1) + ms.row(2) * rot(0, 2)); + res.row(1, ms.row(0) * rot(1, 0) + ms.row(1) * rot(1, 1) + ms.row(2) * rot(1, 2)); + res.row(2, ms.row(0) * rot(2, 0) + ms.row(1) * rot(2, 1) + ms.row(2) * rot(2, 2)); + res.row(3, ms.row(3)); + + return res; +} + + +template +inline +Matrix rotate(const Matrix& ms, const Quaternion& q) +{ + return ms * Matrix(q); +} + +template +inline +Matrix scale(const Matrix& ms, const Vector& vs) +{ + Matrix res; + res.row(0, ms.row(0) * vs[0]); + res.row(1, ms.row(1) * vs[1]); + res.row(2, ms.row(2) * vs[2]); + res.row(3, ms.row(3)); + + return res; +} + + +template +inline +Matrix lookAt(const Vector& eye, const Vector& center, const Vector& up) +{ + const Vector forward {normalize(center - eye)}; + const Vector right {normalize(cross(forward, up))}; + const Vector nup {cross(right, forward)}; + const Vector translation {dot(right, eye), dot(nup, eye), -dot(forward, eye)}; + + Matrix res = Matrix::identity(); + res.row(0, Vector(right, 0)); + res.row(1, Vector(nup, 0)); + res.row(2, Vector(-forward, 0)); + res.col(3, Vector(-translation, static_cast(1))); + + return res; +} + +} diff --git a/source/hpr/math/quaternion.hpp b/source/hpr/math/quaternion.hpp index b488e42..cf9e97a 100644 --- a/source/hpr/math/quaternion.hpp +++ b/source/hpr/math/quaternion.hpp @@ -1,3 +1,3 @@ -#pragma once - -#include "quaternion/quaternion.hpp" \ No newline at end of file +#pragma once + +#include \ No newline at end of file diff --git a/source/hpr/math/quaternion/quaternion.hpp b/source/hpr/math/quaternion/quaternion.hpp index 1180e33..c1cf146 100644 --- a/source/hpr/math/quaternion/quaternion.hpp +++ b/source/hpr/math/quaternion/quaternion.hpp @@ -1,259 +1,291 @@ -#pragma once - -#include "../vector.hpp" - - -namespace hpr -{ - -// Forward declaration - -class Quaternion; - -inline -Quaternion inverse(const Quaternion& q); - -inline -Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs); - -// Class declaration -class Quaternion -{ - -public: - - enum RotationSequence - { - ZYX, ZYZ, ZXY, ZXZ, YXZ, YXY, YZX, YZY, XYZ, XYX, XZY, XZX - }; - -protected: - - scalar p_real; - vec3 p_imag; - -public: - - inline - Quaternion() : - p_real{}, - p_imag{} - {} - - inline - Quaternion(const scalar real, const vec3& imag) : - p_real {real}, - p_imag {imag} - {} - - inline explicit - Quaternion(const scalar real) : - p_real {real}, - p_imag {} - {} - - inline explicit - Quaternion(const vec3& imag) : - p_real {}, - p_imag {imag} - {} - - inline - Quaternion(const vec3& vs, const scalar& theta) : - p_real {cos(0.5 * theta)}, - p_imag {sin(0.5 * theta) * vs / mag(vs)} - {} - - static inline - Quaternion unit(const vec3& vs) - { - return Quaternion(sqrt(1 - norm(vs)), vs); - } - - inline - Quaternion(const RotationSequence rs, const vec3& angles) - { - switch (rs) - { - case XYZ: - *this = Quaternion(vec3(0, 1, 0), angles[0]) * - Quaternion(vec3(0, 1, 0), angles[1]) * - Quaternion(vec3(0, 0, 1), angles[2]); - break; - - default: - throw std::runtime_error("Unknown rotation sequence"); - } - } - - inline - scalar real() const - { - return p_real; - } - - inline - scalar& real() - { - return p_real; - } - - inline - vec3 imag() const - { - return p_imag; - } - - inline - vec3& imag() - { - return p_imag; - } - - inline - void operator+=(const Quaternion& q) - { - p_real += q.p_real; - p_imag += q.p_imag; - } - - inline - void operator-=(const Quaternion& q) - { - p_real -= q.p_real; - p_imag -= q.p_imag; - } - - inline - void operator*=(const Quaternion& q) - { - scalar temp = p_real; - p_real = p_real * q.p_real - dot(p_imag, q.p_imag); - p_imag = temp * q.p_imag + q.p_real * p_imag + cross(p_imag, q.p_imag); - } - - inline - void operator/=(const Quaternion& q) - { - operator*=(inverse(q)); - } - - inline - void operator*=(const scalar s) - { - p_real *= s; - p_imag *= s; - } - - inline - void operator/=(const scalar s) - { - p_real /= s; - p_imag /= s; - } -}; - -inline -bool equal(const Quaternion& lhs, const Quaternion& rhs) -{ - return lhs.real() == rhs.real() && lhs.imag() == rhs.imag(); -} - -inline -bool operator==(const Quaternion& lhs, const Quaternion& rhs) -{ - return equal(lhs, rhs); -} - -inline -bool operator!=(const Quaternion& lhs, const Quaternion& rhs) -{ - return !equal(lhs, rhs); -} - -inline -Quaternion operator+(const Quaternion& lhs, const Quaternion& rhs) -{ - return {lhs.real() + rhs.real(), lhs.imag() + rhs.imag()}; -} - -inline -Quaternion operator-(const Quaternion& q) -{ - return {q.real(), q.imag()}; -} - -inline -Quaternion operator-(const Quaternion& lhs, const Quaternion& rhs) -{ - return {lhs.real() - rhs.real(), lhs.imag() - rhs.imag()}; -} - -inline -Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs) -{ - return {lhs.real() * rhs.real() - dot(lhs.imag(), rhs.imag()), - lhs.real() * rhs.imag() + rhs.real() * lhs.imag() + cross(lhs.imag(), rhs.imag())}; -} - -inline -Quaternion operator/(const Quaternion& lhs, const Quaternion& rhs) -{ - return lhs * inverse(rhs); -} - -inline -Quaternion operator*(const scalar s, const Quaternion& q) -{ - return {s * q.real(), s * q.imag()}; -} - -inline -Quaternion operator*(const Quaternion& q, const scalar s) -{ - return {q.real() * s, q.imag() * s}; -} - -inline -Quaternion operator/(const Quaternion& q, const scalar s) -{ - return {q.real() / s, q.imag() / s}; -} - -inline -scalar norm(const Quaternion& q) -{ - return sqrt(pow(q.real(), 2) + dot(q.imag(), q.imag())); -} - -inline -Quaternion conjugate(const Quaternion& q) -{ - return {q.real(), -q.imag()}; -} - -inline -Quaternion inverse(const Quaternion& q) -{ - return conjugate(q) / pow(norm(q), 2); -} - -inline -Quaternion normalize(const Quaternion& q) -{ - return q / norm(q); -} - -inline -vec3 rotate(const vec3& point, const vec3& axis, const scalar& angle) -{ - Quaternion p {point}; - Quaternion q {normalize(axis), angle}; - return (q * p * inverse(q)).imag(); -} - -// Aliases - -using quat = Quaternion; - +#pragma once + +#include +#include + +namespace hpr +{ + +// Forward declaration + +class Quaternion; + +inline +Quaternion inverse(const Quaternion& q); + +inline +Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs); + +// Class declaration +class Quaternion +{ + +public: + + enum RotationSequence + { + ZYX, ZYZ, ZXY, ZXZ, YXZ, YXY, YZX, YZY, XYZ, XYX, XZY, XZX + }; + +protected: + + scalar p_real; + vec3 p_imag; + +public: + + inline + Quaternion() : + p_real{}, + p_imag{} + {} + + inline + Quaternion(const scalar real, const vec3& imag) : + p_real {real}, + p_imag {imag} + {} + + inline explicit + Quaternion(const scalar real) : + p_real {real}, + p_imag {} + {} + + inline explicit + Quaternion(const vec3& imag) : + p_real {}, + p_imag {imag} + {} + + inline + Quaternion(const vec3& vs, const scalar& theta) : + p_real {cos(0.5 * theta)}, + p_imag {sin(0.5 * theta) * vs / mag(vs)} + {} + + static inline + Quaternion unit(const vec3& vs) + { + return Quaternion(sqrt(1 - norm(vs)), vs); + } + + inline + Quaternion(const RotationSequence rs, const vec3& angles) + { + switch (rs) + { + case XYZ: + *this = Quaternion(vec3(0, 1, 0), angles[0]) * + Quaternion(vec3(0, 1, 0), angles[1]) * + Quaternion(vec3(0, 0, 1), angles[2]); + break; + + default: + throw std::runtime_error("Unknown rotation sequence"); + } + } + + inline + scalar real() const + { + return p_real; + } + + inline + scalar& real() + { + return p_real; + } + + inline + vec3 imag() const + { + return p_imag; + } + + inline + vec3& imag() + { + return p_imag; + } + + inline + scalar& operator[](Size n) + { + if (n > 3) + throw hpr::OutOfRange(); + if (n == 0) + return p_real; + else + return p_imag[n - 1]; + } + + inline + scalar operator[](Size n) const + { + if (n > 3) + throw hpr::OutOfRange(); + if (n == 0) + return p_real; + else + return p_imag[n - 1]; + } + + inline + void operator+=(const Quaternion& q) + { + p_real += q.p_real; + p_imag += q.p_imag; + } + + inline + void operator-=(const Quaternion& q) + { + p_real -= q.p_real; + p_imag -= q.p_imag; + } + + inline + void operator*=(const Quaternion& q) + { + scalar temp = p_real; + p_real = p_real * q.p_real - dot(p_imag, q.p_imag); + p_imag = temp * q.p_imag + q.p_real * p_imag + cross(p_imag, q.p_imag); + } + + inline + void operator/=(const Quaternion& q) + { + operator*=(inverse(q)); + } + + inline + void operator*=(const scalar s) + { + p_real *= s; + p_imag *= s; + } + + inline + void operator/=(const scalar s) + { + p_real /= s; + p_imag /= s; + } +}; + +inline +bool equal(const Quaternion& lhs, const Quaternion& rhs) +{ + return lhs.real() == rhs.real() && lhs.imag() == rhs.imag(); +} + +inline +bool operator==(const Quaternion& lhs, const Quaternion& rhs) +{ + return equal(lhs, rhs); +} + +inline +bool operator!=(const Quaternion& lhs, const Quaternion& rhs) +{ + return !equal(lhs, rhs); +} + +inline +Quaternion operator+(const Quaternion& lhs, const Quaternion& rhs) +{ + return {lhs.real() + rhs.real(), lhs.imag() + rhs.imag()}; +} + +inline +Quaternion operator-(const Quaternion& q) +{ + return {q.real(), q.imag()}; +} + +inline +Quaternion operator-(const Quaternion& lhs, const Quaternion& rhs) +{ + return {lhs.real() - rhs.real(), lhs.imag() - rhs.imag()}; +} + +inline +Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs) +{ + return { + lhs.real() * rhs.real() - dot(lhs.imag(), rhs.imag()), + lhs.real() * rhs.imag() + rhs.real() * lhs.imag() + cross(lhs.imag(), rhs.imag()) + }; +} + +inline +Quaternion operator/(const Quaternion& lhs, const Quaternion& rhs) +{ + return lhs * inverse(rhs); +} + +inline +Quaternion operator*(const scalar s, const Quaternion& q) +{ + return {s * q.real(), s * q.imag()}; +} + +inline +Quaternion operator*(const Quaternion& q, const scalar s) +{ + return {q.real() * s, q.imag() * s}; +} + +inline +Quaternion operator/(const Quaternion& q, const scalar s) +{ + return {q.real() / s, q.imag() / s}; +} + +inline +scalar norm(const Quaternion& q) +{ + return sqrt(pow(q.real(), 2) + dot(q.imag(), q.imag())); +} + +inline +Quaternion conjugate(const Quaternion& q) +{ + return {q.real(), -q.imag()}; +} + +inline +Quaternion inverse(const Quaternion& q) +{ + return conjugate(q) / pow(norm(q), 2); +} + +inline +Quaternion normalize(const Quaternion& q) +{ + return q / norm(q); +} + +inline +vec3 rotate(const vec3& point, const vec3& axis, const scalar& angle) +{ + Quaternion p {point}; + Quaternion q {normalize(axis), angle}; + return (q * p * inverse(q)).imag(); +} + +void decompose(const Quaternion& q, vec3& axis, scalar& angle) +{ + const scalar qnorm = norm(q.imag()); + axis = q.imag() / qnorm; + angle = 2 * atan2(qnorm, q.real()); +} + + +// Aliases + +using quat = Quaternion; + } \ No newline at end of file diff --git a/source/hpr/math/scalar.hpp b/source/hpr/math/scalar.hpp index 1b8a4e1..a63eaba 100644 --- a/source/hpr/math/scalar.hpp +++ b/source/hpr/math/scalar.hpp @@ -1,3 +1,3 @@ -#pragma once - -#include "scalar/scalar.hpp" +#pragma once + +#include diff --git a/source/hpr/math/scalar/scalar.hpp b/source/hpr/math/scalar/scalar.hpp index 3583857..c9f39c5 100644 --- a/source/hpr/math/scalar/scalar.hpp +++ b/source/hpr/math/scalar/scalar.hpp @@ -2,6 +2,8 @@ #include #include +#include + namespace hpr { @@ -83,7 +85,7 @@ public: // properties -protected: +/*protected: static value_type s_precision; @@ -95,23 +97,44 @@ public: static constexpr Scalar inf() { return std::numeric_limits::infinity(); } - static constexpr Scalar epsilon() { return std::numeric_limits::epsilon(); } + static constexpr Scalar epsilon() { return std::numeric_limits::epsilon(); }*/ }; // specialization type +#if defined(HPR_SCALAR_IMPLEMENTAION) #if defined(HPR_SCALAR_LONGDOUBLE) + using scalar_type = long double; using scalar = Scalar; #elif defined(HPR_SCALAR_DOUBLE) + using scalar_type = double; using scalar = Scalar; #elif defined(HPR_SCALAR_FLOAT) + using scalar_type = float; using scalar = Scalar; #else + using scalar_type = double; using scalar = Scalar; #endif +#else +#if defined(HPR_SCALAR_LONGDOUBLE) + using scalar_type = long double; + using scalar = long double; +#elif defined(HPR_SCALAR_DOUBLE) + using scalar_type = double; + using scalar = double; +#elif defined(HPR_SCALAR_FLOAT) + using scalar_type = float; + using scalar = float; +#else + using scalar_type = float; + using scalar = float; +#endif +#endif // +#if defined(HPR_SCALAR_IMPLEMENTATION) template<> scalar::value_type scalar::s_precision = static_cast(1e-15); // global operators @@ -141,6 +164,8 @@ constexpr bool operator<(const scalar& lhs, const scalar& rhs) { return lhs.valu constexpr bool operator>=(const scalar& lhs, const scalar& rhs) { return lhs.value() >= rhs.value(); } constexpr bool operator<=(const scalar& lhs, const scalar& rhs) { return lhs.value() <= rhs.value(); } +//std::ostream& operator<<(std::ostream& stream, const scalar& s) { return stream << s.value(); } + /// scalar vs Scalar template constexpr scalar& operator+=(scalar& lhs, const Scalar& rhs) { lhs.value() += static_cast(rhs.value()); return lhs; } @@ -215,67 +240,103 @@ template constexpr bool operator>=(const T& lhs, const scalar& rhs) { template constexpr bool operator<=(const scalar& lhs, const T& rhs) { return lhs.value() <= static_cast(rhs); } template constexpr bool operator<=(const T& lhs, const scalar& rhs) { return static_cast(lhs) <= rhs.value(); } +#endif + // transcendentals -template constexpr scalar cos(const T& s) { return std::cos(static_cast(s));} +template constexpr scalar cos(const T& s) { return std::cos(static_cast(s));} -template constexpr scalar acos(const T& s) { return std::acos(scalar(s).value()); } +template constexpr scalar acos(const T& s) { return std::acos(static_cast(s)); } -template constexpr scalar cosh(const T& s) { return std::cosh(scalar(s).value()); } +template constexpr scalar cosh(const T& s) { return std::cosh(static_cast(s)); } -template constexpr scalar acosh(const T& s) { return std::acosh(scalar(s).value()); } +template constexpr scalar acosh(const T& s) { return std::acosh(static_cast(s)); } -template constexpr scalar sin(const T& s) { return std::sin(scalar(s).value()); } +template constexpr scalar sin(const T& s) { return std::sin(static_cast(s)); } -template constexpr scalar asin(const T& s) { return std::asin(scalar(s).value()); } +template constexpr scalar asin(const T& s) { return std::asin(static_cast(s)); } -template constexpr scalar sinh(const T& s) { return std::sinh(scalar(s).value()); } +template constexpr scalar sinh(const T& s) { return std::sinh(static_cast(s)); } -template constexpr scalar asinh(const T& s) { return std::asinh(scalar(s).value()); } +template constexpr scalar asinh(const T& s) { return std::asinh(static_cast(s)); } -template constexpr scalar tan(const T& s) { return std::tan(scalar(s).value()); } +template constexpr scalar tan(const T& s) { return std::tan(static_cast(s)); } -template constexpr scalar atan(const T& s) { return std::atan(scalar(s).value()); } +template constexpr scalar atan(const T& s) { return std::atan(static_cast(s)); } -template constexpr scalar tanh(const T& s) { return std::tanh(scalar(s).value()); } +template constexpr scalar atan2(const T& s, const X& s2) { return std::atan2(static_cast(s), static_cast(s2)); } -template constexpr scalar atanh(const T& s) { return std::atanh(scalar(s).value()); } +template constexpr scalar tanh(const T& s) { return std::tanh(static_cast(s)); } -template constexpr scalar exp(const T& s) { return std::exp(scalar(s).value()); } +template constexpr scalar atanh(const T& s) { return std::atanh(static_cast(s)); } -template constexpr scalar log(const T& s) { return std::log(scalar(s).value()); } +template constexpr scalar exp(const T& s) { return std::exp(static_cast(s)); } -template constexpr scalar log10(const T& s) { return std::log10(scalar(s).value()); } +template constexpr scalar log(const T& s) { return std::log(static_cast(s)); } -template constexpr scalar pow(const T& s, const X& d) { return std::pow(scalar(s).value(), scalar(d).value()); } +template constexpr scalar log10(const T& s) { return std::log10(static_cast(s)); } -template constexpr scalar sqrt(const T& s) { return std::sqrt(scalar(s).value()); } +template constexpr scalar pow(const T& s, const X& d) { return std::pow(static_cast(s), static_cast(d)); } -template constexpr scalar isqrt(const T& s) { return static_cast(1) / sqrt(scalar(s).value()); } +template constexpr scalar sqrt(const T& s) { return std::sqrt(static_cast(s)); } + +template constexpr scalar isqrt(const T& s) { return static_cast(1) / sqrt(static_cast(s)); } // constants -constexpr inline scalar pi() { return std::numbers::pi_v; } +constexpr scalar pi() { return std::numbers::pi_v; } -constexpr inline scalar e() { return std::numbers::e_v; } +constexpr scalar e() { return std::numbers::e_v; } // etc -constexpr scalar abs(const scalar& s) { return std::abs(s.value()); } +static float _scalar_float_precision = 1e-15f; +static double _scalar_double_precision = 1e-15; +static long double _scalar_long_double_precision = 1e-15l; -constexpr scalar mag(const scalar& s) { return std::abs(s.value()); } +template inline void precision(const T& precision) +{ + if constexpr (std::is_same::value) + _scalar_float_precision = precision; + else if constexpr (std::is_same::value) + _scalar_double_precision = precision; + else if constexpr (std::is_same::value) + _scalar_long_double_precision = precision; +} -constexpr bool equal(const scalar& lhs, const scalar& rhs, const scalar& precision = scalar::precision()) { return abs(lhs - rhs) < precision; } +template inline T precision() +{ + if constexpr (std::is_same::value) + return _scalar_float_precision; + else if constexpr (std::is_same::value) + return _scalar_double_precision; + else if constexpr (std::is_same::value) + return _scalar_long_double_precision; +} + + + +constexpr scalar inf() { return std::numeric_limits::infinity(); } + +constexpr scalar epsilon() { return std::numeric_limits::epsilon(); } + +constexpr scalar isnan(const scalar& s) { return std::isnan(static_cast(s)); } + +constexpr scalar abs(const scalar& s) { return std::abs(static_cast(s)); } + +constexpr scalar mag(const scalar& s) { return std::abs(static_cast(s)); } + +constexpr bool equal(const scalar& lhs, const scalar& rhs, const scalar& precision = hpr::precision()) { return abs(lhs - rhs) < precision; } //! Convert degrees to radians -constexpr scalar rad(const scalar& s) { return s * pi() / static_cast(180); } +constexpr scalar rad(const scalar& s) { return s * pi() / static_cast(180); } //! Convert radians to degrees -constexpr scalar deg(const scalar& s) { return s * static_cast(180) / pi(); } +constexpr scalar deg(const scalar& s) { return s * static_cast(180) / pi(); } -constexpr scalar min(const scalar& s1, const scalar& s2) { return std::min(s1.value(),s2.value());} +constexpr scalar min(const scalar& s1, const scalar& s2) { return std::min(static_cast(s1), static_cast(s2));} -constexpr scalar max(const scalar& s1, const scalar& s2) { return std::max(s1.value(), s2.value()); } +constexpr scalar max(const scalar& s1, const scalar& s2) { return std::max(static_cast(s1), static_cast(s2)); } constexpr scalar clip(const scalar& s, const scalar& sMin, const scalar& sMax) { return min(sMax, max(s, sMin)); } diff --git a/source/hpr/math/tests/CMakeLists.txt b/source/hpr/math/tests/CMakeLists.txt deleted file mode 100644 index 1644c07..0000000 --- a/source/hpr/math/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -file(GLOB tests_cpp "*.cpp") - -add_executable(${PROJECT_NAME}-tests - ${tests_cpp} -) - -target_link_libraries(${PROJECT_NAME}-tests - PUBLIC - hpr::${PROJECT_NAME} - PRIVATE - GTest::gtest_main -) - -gtest_add_tests(TARGET ${PROJECT_NAME}-tests) diff --git a/source/hpr/math/tests/math-test.cpp b/source/hpr/math/tests/math-test.cpp index 26e8df8..3d7083c 100644 --- a/source/hpr/math/tests/math-test.cpp +++ b/source/hpr/math/tests/math-test.cpp @@ -1,22 +1,25 @@ #include -#include "../vector.hpp" -#include "../matrix.hpp" -#include "../quaternion.hpp" +#include +#include +#include + #include + TEST(math, Scalar) { EXPECT_EQ(hpr::scalar(5) + hpr::scalar(7), hpr::scalar(12)); EXPECT_TRUE(std::is_floating_point_v>); EXPECT_TRUE(std::is_arithmetic_v>); - EXPECT_EQ(5.f, hpr::Scalar(5)); + //EXPECT_EQ(5.f, hpr::Scalar(5)); EXPECT_EQ(hpr::rad(180), hpr::pi()); EXPECT_EQ(hpr::deg(hpr::pi()), 180); EXPECT_EQ(hpr::cos(0), 1); EXPECT_EQ(hpr::sin(0), 0); EXPECT_EQ(hpr::abs(hpr::scalar(-1)), 1); EXPECT_EQ(hpr::pow(2, 2), 4); + EXPECT_EQ(hpr::precision(), static_cast(1e-15)); EXPECT_TRUE(typeid(static_cast(hpr::scalar(5))) == typeid(float)); EXPECT_FALSE(!hpr::scalar(-1.)); @@ -87,4 +90,5 @@ TEST(math, Quaternion) hpr::quat q; hpr::vec3 np = hpr::rotate(hpr::vec3(0, 1, 0), {1, 0, 0}, hpr::pi() * 0.5); EXPECT_TRUE(hpr::all(hpr::equal(np, hpr::vec3(0, 0, 1)))); + EXPECT_EQ(hpr::norm(hpr::quat(np)), 1); } \ No newline at end of file diff --git a/source/hpr/math/vector.hpp b/source/hpr/math/vector.hpp index 2f33a32..dc94473 100644 --- a/source/hpr/math/vector.hpp +++ b/source/hpr/math/vector.hpp @@ -1,4 +1,4 @@ #pragma once -#include "vector/vector_space.hpp" +#include diff --git a/source/hpr/math/vector/vector_space.hpp b/source/hpr/math/vector/vector.hpp similarity index 95% rename from source/hpr/math/vector/vector_space.hpp rename to source/hpr/math/vector/vector.hpp index fe37183..1dc1314 100644 --- a/source/hpr/math/vector/vector_space.hpp +++ b/source/hpr/math/vector/vector.hpp @@ -1,328 +1,328 @@ -#pragma once - -#include "../integer.hpp" -#include "../scalar.hpp" -#include "../../containers/array/static_array.hpp" - - -namespace hpr -{ - -// forward declarations - -template requires (S >= 0) -class Vector; - -template -using SubVector = typename std::conditional= 2, Vector, Vector>::type; - -// type traits - -template -struct is_vector : public std::false_type {}; - -template -struct is_vector> : public std::true_type {}; - -// concepts - -template -concept IsVector = is_vector::value; - -} - -namespace hpr -{ - -template requires (S >= 0) -class Vector : public StaticArray -{ - - using base = StaticArray; - -public: - - using value_type = Type; - using size_type = Size; - using pointer = Type*; - using reference = Type&; - using iterator = Iterator; - using const_iterator = Iterator; - -public: - - //! null constructor - constexpr - Vector() : - base {} - {} - - //! copy constructor - constexpr - Vector(const Vector& vs) : - base {static_cast(vs)} - {} - - //! move constructor - constexpr - Vector(Vector&& vs) noexcept : - base {std::forward(static_cast(vs))} - {} - - //! copy assignment operator - constexpr - Vector& operator=(const Vector& vs) - { - base::operator=(vs); - return *this; - } - - //! move assignment operator - constexpr - Vector& operator=(Vector&& vs) noexcept - { - swap(*this, vs); - return *this; - } - - //! destructor - virtual - ~Vector() = default; - - //! copy constructor from base - constexpr - Vector(const base& arr) : - base {arr} - {} - - //! move constructor from base - constexpr - Vector(base&& arr) : - base {std::forward(arr)} - {} - - //! construct from iterators - constexpr - Vector(typename base::iterator start, typename base::iterator end) : - base {start, end} - {} - - //! construct from constant iterators - constexpr - Vector(typename base::const_iterator start, typename base::const_iterator end) : - base {start, end} - {} - - //! construct from initializer list - constexpr - Vector(std::initializer_list list) : - base {list} - {} - - //! copy constructor with variadic args - template - constexpr - Vector(const value_type& v, const Args& ...args) requires (S == 1 + sizeof...(args)): - base {v, static_cast(args)...} - {} - - //! move constructor with variadic args - template - constexpr - Vector(value_type&& v, Args&& ...args) requires (S == 1 + sizeof...(args)): - base {v, static_cast(std::forward(args))...} - {} - - //! copy constructor with sub vector and value - constexpr - Vector(const SubVector& svs, const value_type& v) requires (S >= 1): - base {} - { - for (auto n = 0; n < svs.size(); ++n) - (*this)[n] = svs[n]; - (*this)[svs.size()] = v; - } - - //! copy constructor from greater vector - template requires (GS > S) - constexpr explicit - Vector(const Vector& vs) : - base {vs.begin(), vs.begin() + S} - {} - -}; - -// global operators - -template inline Vector operator+(const Vector& lhs) { Vector vs; for (Size n = 0; n < S; ++n) vs[n] = lhs[n]; return vs; } -template inline Vector operator-(const Vector& lhs) { Vector vs; for (Size n = 0; n < S; ++n) vs[n] = -lhs[n]; return vs; } - -template inline Vector& operator+=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] += rhs[n]; return lhs; } -template inline Vector& operator-=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] -= rhs[n]; return lhs; } -template inline Vector& operator*=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] *= rhs[n]; return lhs; } -template inline Vector& operator/=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] /= rhs[n]; return lhs; } - -template inline Vector operator+(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] += rhs[n]; return vs; } -template inline Vector operator-(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] -= rhs[n]; return vs; } -template inline Vector operator*(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] *= rhs[n]; return vs; } -template inline Vector operator/(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] /= rhs[n]; return vs; } - -template inline bool operator==(const Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) if (lhs[n] != rhs[n]) return false; return true; } -template inline bool operator!=(const Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) if (lhs[n] == rhs[n]) return false; return true; } - - -template inline Vector& operator+=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] += rhs; return lhs; } -template inline Vector& operator-=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] -= rhs; return lhs; } -template inline Vector& operator*=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] *= rhs; return lhs; } -template inline Vector& operator/=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] /= rhs; return lhs; } - -template inline Vector operator+(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] += rhs; return vs; } -template inline Vector operator-(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] -= rhs; return vs; } -template inline Vector operator*(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] *= rhs; return vs; } -template inline Vector operator/(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] /= rhs; return vs; } - -template inline Vector operator+(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] += lhs; return vs; } -template inline Vector operator-(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] -= lhs; return vs; } -template inline Vector operator*(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] *= lhs; return vs; } -template inline Vector operator/(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] /= lhs; return vs; } - -// boolean operations - -template -inline -Vector equal(const Vector& lhs, const Vector& rhs, const scalar& precision = scalar::precision()) -{ - Vector vs; - for (auto n = 0; n < S; ++n) - vs[n] = equal(lhs[n], rhs[n], precision); - return vs; -} - -template -inline -bool any(const Vector& vs) -{ - bool res = false; - for (auto e : vs) - res = res || e; - return res; -} - -template -inline -bool all(const Vector& vs) -{ - bool res = true; - for (auto e : vs) - res = res && e; - return res; -} - -// per element operations - -template -inline -Vector abs(const Vector& vs) -{ - Vector res; - for (auto n = 0; n < S; ++n) - res[n] = abs(vs[n]); - return res; -} - -template -inline -Type sum(const Vector& vs) -{ - Type sum {}; - for (const Type& v : vs) - sum += v; - return sum; -} - -template -inline -Vector pow(const Vector& vs, scalar degree) -{ - Vector res; - for (auto n = 0; n < S; ++n) - res[n] = pow(vs[n], degree); - return res; -} - -// vector operations - -template -inline -Type norm(const Vector& vs) -{ - return sqrt(sum(pow(abs(vs), 2))); -} - -template -inline -Type dot(const Vector& lhs, const Vector& rhs) -{ - return sum(lhs * rhs); -} - -template -inline -Type length(const Vector& vs) -{ - return sqrt(dot(vs, vs)); -} - -template -inline -Type mag(const Vector& vs) -{ - return length(vs); -} - -template -inline -Type distance(const Vector& vs1, const Vector& vs2) -{ - return length(vs1 - vs2); -} - -template -inline -Vector normalize(const Vector& vs) -{ - return vs * isqrt(dot(vs, vs)); -} - -template -inline -Type angle(const Vector& lhs, const Vector& rhs) -{ - scalar cos = dot(lhs, rhs) / (norm(lhs) * norm(rhs)); - return acos(cos); //clip(cos, -1., 1.)); -} - -// vector 3 operations - -template -inline -Vector cross(const Vector& lhs, const Vector& rhs) -{ - return Vector( - lhs[1] * rhs[2] - lhs[2] * rhs[1], - lhs[2] * rhs[0] - lhs[0] * rhs[2], - lhs[0] * rhs[1] - lhs[1] * rhs[0] - ); -} - - -// aliases - -template -using vec = Vector; - -using vec2 = Vector; -using vec3 = Vector; -using vec4 = Vector; - -} +#pragma once + +#include +#include +#include + + +namespace hpr +{ + +// forward declarations + +template requires (S >= 0) +class Vector; + +template +using SubVector = typename std::conditional= 2, Vector, Vector>::type; + +// type traits + +template +struct is_vector : public std::false_type {}; + +template +struct is_vector> : public std::true_type {}; + +// concepts + +template +concept IsVector = is_vector::value; + +} + +namespace hpr +{ + +template requires (S >= 0) +class Vector : public StaticArray +{ + + using base = StaticArray; + +public: + + using value_type = Type; + using size_type = Size; + using pointer = Type*; + using reference = Type&; + using iterator = Iterator; + using const_iterator = Iterator; + +public: + + //! null constructor + constexpr + Vector() : + base {} + {} + + //! copy constructor + constexpr + Vector(const Vector& vs) : + base {static_cast(vs)} + {} + + //! move constructor + constexpr + Vector(Vector&& vs) noexcept : + base {std::forward(static_cast(vs))} + {} + + //! copy assignment operator + constexpr + Vector& operator=(const Vector& vs) + { + base::operator=(vs); + return *this; + } + + //! move assignment operator + constexpr + Vector& operator=(Vector&& vs) noexcept + { + swap(*this, vs); + return *this; + } + + //! destructor + virtual + ~Vector() = default; + + //! copy constructor from base + constexpr + Vector(const base& arr) : + base {arr} + {} + + //! move constructor from base + constexpr + Vector(base&& arr) : + base {std::forward(arr)} + {} + + //! construct from iterators + constexpr + Vector(typename base::iterator start, typename base::iterator end) : + base {start, end} + {} + + //! construct from constant iterators + constexpr + Vector(typename base::const_iterator start, typename base::const_iterator end) : + base {start, end} + {} + + //! construct from initializer list + constexpr + Vector(std::initializer_list list) : + base {list} + {} + + //! copy constructor with variadic args + template + constexpr + Vector(const value_type& v, const Args& ...args) requires (S == 1 + sizeof...(args)): + base {v, static_cast(args)...} + {} + + //! move constructor with variadic args + template + constexpr + Vector(value_type&& v, Args&& ...args) requires (S == 1 + sizeof...(args)): + base {v, static_cast(std::forward(args))...} + {} + + //! copy constructor with sub vector and value + constexpr + Vector(const SubVector& svs, const value_type& v) requires (S >= 1): + base {} + { + for (auto n = 0; n < svs.size(); ++n) + (*this)[n] = svs[n]; + (*this)[svs.size()] = v; + } + + //! copy constructor from greater vector + template requires (GS > S) + constexpr explicit + Vector(const Vector& vs) : + base {vs.begin(), vs.begin() + S} + {} + +}; + +// global operators + +template inline Vector operator+(const Vector& lhs) { Vector vs; for (Size n = 0; n < S; ++n) vs[n] = lhs[n]; return vs; } +template inline Vector operator-(const Vector& lhs) { Vector vs; for (Size n = 0; n < S; ++n) vs[n] = -lhs[n]; return vs; } + +template inline Vector& operator+=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] += rhs[n]; return lhs; } +template inline Vector& operator-=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] -= rhs[n]; return lhs; } +template inline Vector& operator*=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] *= rhs[n]; return lhs; } +template inline Vector& operator/=(Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) lhs[n] /= rhs[n]; return lhs; } + +template inline Vector operator+(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] += rhs[n]; return vs; } +template inline Vector operator-(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] -= rhs[n]; return vs; } +template inline Vector operator*(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] *= rhs[n]; return vs; } +template inline Vector operator/(const Vector& lhs, const Vector& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] /= rhs[n]; return vs; } + +template inline bool operator==(const Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) if (lhs[n] != rhs[n]) return false; return true; } +template inline bool operator!=(const Vector& lhs, const Vector& rhs) { for (Size n = 0; n < S; ++n) if (lhs[n] == rhs[n]) return false; return true; } + + +template inline Vector& operator+=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] += rhs; return lhs; } +template inline Vector& operator-=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] -= rhs; return lhs; } +template inline Vector& operator*=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] *= rhs; return lhs; } +template inline Vector& operator/=(Vector& lhs, const T& rhs) { for (Size n = 0; n < S; ++n) lhs[n] /= rhs; return lhs; } + +template inline Vector operator+(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] += rhs; return vs; } +template inline Vector operator-(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] -= rhs; return vs; } +template inline Vector operator*(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] *= rhs; return vs; } +template inline Vector operator/(const Vector& lhs, const T& rhs) { Vector vs {lhs}; for (Size n = 0; n < S; ++n) vs[n] /= rhs; return vs; } + +template inline Vector operator+(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] += lhs; return vs; } +template inline Vector operator-(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] -= lhs; return vs; } +template inline Vector operator*(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] *= lhs; return vs; } +template inline Vector operator/(const T& lhs, const Vector& rhs) { Vector vs {rhs}; for (Size n = 0; n < S; ++n) vs[n] /= lhs; return vs; } + +// boolean operations + +template +inline +Vector equal(const Vector& lhs, const Vector& rhs, const scalar& precision = hpr::precision()) +{ + Vector vs; + for (auto n = 0; n < S; ++n) + vs[n] = equal(lhs[n], rhs[n], precision); + return vs; +} + +template +inline +bool any(const Vector& vs) +{ + bool res = false; + for (auto e : vs) + res = res || e; + return res; +} + +template +inline +bool all(const Vector& vs) +{ + bool res = true; + for (auto e : vs) + res = res && e; + return res; +} + +// per element operations + +template +inline +Vector abs(const Vector& vs) +{ + Vector res; + for (auto n = 0; n < S; ++n) + res[n] = abs(vs[n]); + return res; +} + +template +inline +Type sum(const Vector& vs) +{ + Type sum {}; + for (const Type& v : vs) + sum += v; + return sum; +} + +template +inline +Vector pow(const Vector& vs, scalar degree) +{ + Vector res; + for (auto n = 0; n < S; ++n) + res[n] = pow(vs[n], degree); + return res; +} + +// vector operations + +template +inline +Type norm(const Vector& vs) +{ + return sqrt(sum(pow(abs(vs), 2))); +} + +template +inline +Type dot(const Vector& lhs, const Vector& rhs) +{ + return sum(lhs * rhs); +} + +template +inline +Type length(const Vector& vs) +{ + return sqrt(dot(vs, vs)); +} + +template +inline +Type mag(const Vector& vs) +{ + return length(vs); +} + +template +inline +Type distance(const Vector& vs1, const Vector& vs2) +{ + return length(vs1 - vs2); +} + +template +inline +Vector normalize(const Vector& vs) +{ + return vs * isqrt(dot(vs, vs)); +} + +template +inline +Type angle(const Vector& lhs, const Vector& rhs) +{ + scalar cos = dot(lhs, rhs) / (norm(lhs) * norm(rhs)); + return acos(cos); //clip(cos, -1., 1.)); +} + +// vector 3 operations + +template +inline +Vector cross(const Vector& lhs, const Vector& rhs) +{ + return Vector( + lhs[1] * rhs[2] - lhs[2] * rhs[1], + lhs[2] * rhs[0] - lhs[0] * rhs[2], + lhs[0] * rhs[1] - lhs[1] * rhs[0] + ); +} + + +// aliases + +template +using vec = Vector; + +using vec2 = Vector; +using vec3 = Vector; +using vec4 = Vector; + +} diff --git a/source/hpr/math/vector/vector_space.cpp b/source/hpr/math/vector/vector_space.cpp deleted file mode 100644 index 56a2e66..0000000 --- a/source/hpr/math/vector/vector_space.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// -// Created by L-Nafaryus on 10/17/2022. -// -#include "vector_space.hpp" \ No newline at end of file diff --git a/source/hpr/mesh.hpp b/source/hpr/mesh.hpp index 3b00b03..600fd1c 100644 --- a/source/hpr/mesh.hpp +++ b/source/hpr/mesh.hpp @@ -1,3 +1,3 @@ -#pragma once - -#include "mesh/mesh.hpp" \ No newline at end of file +#pragma once + +#include \ No newline at end of file diff --git a/source/hpr/mesh/CMakeLists.txt b/source/hpr/mesh/CMakeLists.txt index 021c16f..4ad5286 100644 --- a/source/hpr/mesh/CMakeLists.txt +++ b/source/hpr/mesh/CMakeLists.txt @@ -1,70 +1,29 @@ cmake_minimum_required(VERSION 3.16) project(mesh - VERSION "${HPR_PROJECT_VERSION}" + VERSION "${HPR_VERSION}" LANGUAGES CXX ) -add_library(${PROJECT_NAME}) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) +hpr_add_library(${PROJECT_NAME} STATIC) -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../mesh.hpp" "*.hpp" - ) +hpr_collect_interface(${PROJECT_NAME} + "../mesh.hpp" + "*.hpp" +) -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES - "*.cpp" - ) +hpr_collect_sources(${PROJECT_NAME} + "*.cpp" +) target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ - PRIVATE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES} - ) - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> + PRIVATE ${${PROJECT_NAME}_SOURCES} ) -if(HPR_TEST) - add_subdirectory(tests) -endif() +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) +hpr_tests(${PROJECT_NAME} tests) + diff --git a/source/hpr/mesh/cell.hpp b/source/hpr/mesh/cell.hpp index cc30687..026bdfa 100644 --- a/source/hpr/mesh/cell.hpp +++ b/source/hpr/mesh/cell.hpp @@ -1,38 +1,38 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar/scalar.hpp" -#include "../math/vector.hpp" - - -namespace hpr::mesh -{ - -class Face; - -class Cell : public darray -{ - - using face_pointer = Face*; - using base = darray; - -public: - - Cell() : - base {} - {} - - Cell(std::initializer_list faces) : - base{faces} - {} - - ~Cell() override = default; - - darray& faces() - { - return *this; - } - -}; - +#pragma once + +#include +#include +#include + + +namespace hpr::mesh +{ + +class Face; + +class Cell : public darray +{ + + using face_pointer = Face*; + using base = darray; + +public: + + Cell() : + base {} + {} + + Cell(std::initializer_list faces) : + base{faces} + {} + + ~Cell() override = default; + + darray& faces() + { + return *this; + } + +}; + } \ No newline at end of file diff --git a/source/hpr/mesh/edge.hpp b/source/hpr/mesh/edge.hpp index 36ca52c..4364d05 100644 --- a/source/hpr/mesh/edge.hpp +++ b/source/hpr/mesh/edge.hpp @@ -1,66 +1,66 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar/scalar.hpp" -#include "../math/vector.hpp" - - - -namespace hpr::mesh -{ - -class Vertex; -class Face; - -class Edge : public sarray -{ - friend class Mesh; - - using vertex_pointer = Vertex*; - using base = sarray; - -protected: - darray p_refFaces; - -public: - Edge() : - base{} - {} - - Edge(vertex_pointer v1, vertex_pointer v2) : - base{v1, v2} - {} - - ~Edge() override - { - for (auto& f: p_refFaces) - f = nullptr; - } - - darray& refFaces() - { - return p_refFaces; - } - - void addRefFace(Face* face) - { - p_refFaces.push(face); - } - - sarray& vertices() - { - return *this; - } - - vertex_pointer vertex(size_type n) - { - return (*this)[n]; - } - - bool isValid() - { - return *front() != *back(); - } -}; - +#pragma once + +#include +#include +#include + + + +namespace hpr::mesh +{ + +class Vertex; +class Face; + +class Edge : public sarray +{ + friend class Mesh; + + using vertex_pointer = Vertex*; + using base = sarray; + +protected: + darray p_refFaces; + +public: + Edge() : + base{} + {} + + Edge(vertex_pointer v1, vertex_pointer v2) : + base{v1, v2} + {} + + ~Edge() override + { + for (auto& f: p_refFaces) + f = nullptr; + } + + darray& refFaces() + { + return p_refFaces; + } + + void addRefFace(Face* face) + { + p_refFaces.push(face); + } + + sarray& vertices() + { + return *this; + } + + vertex_pointer vertex(size_type n) + { + return (*this)[n]; + } + + bool isValid() + { + return *front() != *back(); + } +}; + } \ No newline at end of file diff --git a/source/hpr/mesh/face.hpp b/source/hpr/mesh/face.hpp index b3eb833..8f3cb61 100644 --- a/source/hpr/mesh/face.hpp +++ b/source/hpr/mesh/face.hpp @@ -1,72 +1,72 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar/scalar.hpp" -#include "../math/vector.hpp" - - - -namespace hpr::mesh -{ - -class Vertex; -class Edge; -class Cell; - -class Face : public darray -{ - friend class Mesh; - - using edge_pointer = Edge*; - using vertex_pointer = Vertex*; - using base = darray; - -protected: - darray p_refCells; - -public: - Face() : - base{} - {} - - Face(std::initializer_list edges) : - base{edges} - {} - - ~Face() override - { - for (auto& c: p_refCells) - c = nullptr; - } - - darray& refCells() - { - return p_refCells; - } - - void addRefCell(Cell* cell) - { - p_refCells.push(cell); - } - - darray& edges() - { - return *this; - } - - edge_pointer edge(size_type n) - { - return (*this)[n]; - } - - darray vertices() - { - darray vertices_ {size(), nullptr}; - for (auto n = 0; n < size(); ++n) - vertices_[n] = edge(n)->vertex(0); - return vertices_; - } - -}; - +#pragma once + +#include +#include +#include + + + +namespace hpr::mesh +{ + +class Vertex; +class Edge; +class Cell; + +class Face : public darray +{ + friend class Mesh; + + using edge_pointer = Edge*; + using vertex_pointer = Vertex*; + using base = darray; + +protected: + darray p_refCells; + +public: + Face() : + base{} + {} + + Face(std::initializer_list edges) : + base{edges} + {} + + ~Face() override + { + for (auto& c: p_refCells) + c = nullptr; + } + + darray& refCells() + { + return p_refCells; + } + + void addRefCell(Cell* cell) + { + p_refCells.push(cell); + } + + darray& edges() + { + return *this; + } + + edge_pointer edge(size_type n) + { + return (*this)[n]; + } + + darray vertices() + { + darray vertices_ {size(), nullptr}; + for (auto n = 0; n < size(); ++n) + vertices_[n] = edge(n)->vertex(0); + return vertices_; + } + +}; + } \ No newline at end of file diff --git a/source/hpr/mesh/mesh.cpp b/source/hpr/mesh/mesh.cpp index 724b70e..e10b429 100644 --- a/source/hpr/mesh/mesh.cpp +++ b/source/hpr/mesh/mesh.cpp @@ -1 +1 @@ -#include "mesh.hpp" +#include diff --git a/source/hpr/mesh/mesh.hpp b/source/hpr/mesh/mesh.hpp index 13a5d02..3efb682 100644 --- a/source/hpr/mesh/mesh.hpp +++ b/source/hpr/mesh/mesh.hpp @@ -1,229 +1,229 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar/scalar.hpp" -#include "../math/vector.hpp" - -#include "vertex.hpp" -#include "edge.hpp" -#include "face.hpp" -#include "cell.hpp" - - -namespace hpr::mesh -{ - -class Mesh -{ - -public: - using size_type = std::size_t; - using vertex_pointer = Vertex*; - using edge_pointer = Edge*; - using face_pointer = Face*; - using cell_pointer = Cell*; - -protected: - darray p_vertices; - darray p_edges; - darray p_faces; - darray p_cells; - -public: - - Mesh() = default; - - Mesh(const Mesh&) = default; - - ~Mesh() - { - for (auto& v : p_vertices) - delete v; - for (auto& e : p_edges) - delete e; - for (auto& f : p_faces) - delete f; - for (auto& c : p_cells) - delete c; - } - - int indexOf(vertex_pointer v) - { - for (int n = 0; n < vertices().size(); ++n) - if (vertex(n) == v) - return n; - return -1; - } - - int indexOf(edge_pointer e) - { - for (int n = 0; n < edges().size(); ++n) - if (edge(n) == e) - return n; - return -1; - } - - int indexOf(face_pointer e) - { - for (int n = 0; n < faces().size(); ++n) - if (face(n) == e) - return n; - return -1; - } - - int indexOf(cell_pointer e) - { - for (int n = 0; n < cells().size(); ++n) - if (cell(n) == e) - return n; - return -1; - } - - darray& vertices() - { - return p_vertices; - } - - [[nodiscard]] - vertex_pointer vertex(size_type n) const - { - return p_vertices[n]; - } - - void addVertex(const scalar& x, const scalar& y, const scalar& z) - { - p_vertices.push(new Vertex(x, y, z)); - } - - void removeNullVertices() - { - vertices().remove([](vertex_pointer vertex){ return vertex == nullptr; }); - } - - void removeVertex(size_type n, bool erase = true, bool cascade = true) - { - if (cascade) - { - for (auto &refEdge: vertex(n)->refEdges()) - removeEdge(indexOf(refEdge), false); - removeNullEdges(); - } - delete vertex(n); - if (erase) - vertices().remove(n); - } - - darray& edges() - { - return p_edges; - } - - [[nodiscard]] - edge_pointer edge(size_type n) const - { - return p_edges[n]; - } - - void addEdge(vertex_pointer v1, vertex_pointer v2) - { - edges().push(new Edge {v1, v2}); - v1->addRefEdge(edges().back()); - v2->addRefEdge(edges().back()); - } - - void removeNullEdges() - { - edges().remove([](edge_pointer edge){ return edge == nullptr; }); - } - - void removeEdge(size_type n, bool erase = true, bool cascade = true) - { - if (cascade) - { - for (auto &refFace: edge(n)->refFaces()) - removeFace(indexOf(refFace), false); - removeNullFaces(); - } - for (auto& vertex : edge(n)->vertices()) - vertex->refEdges().remove([this, n](edge_pointer e){ return e == edge(n); }); - delete edge(n); - if (erase) - edges().remove(n); - } - - darray& faces() - { - return p_faces; - } - - [[nodiscard]] - face_pointer face(size_type n) const - { - return p_faces[n]; - } - - template ... Edges> - void addFace(const Edges& ...edges) - { - faces().push(new Face {static_cast(edges)...}); - for (auto& edge : *faces().back()) - edge->addRefFace(faces().back()); - } - - void removeNullFaces() - { - faces().remove([](face_pointer face){ return face == nullptr; }); - } - - void removeFace(size_type n, bool erase = true, bool cascade = true) - { - if (cascade) - { - for (auto &refCell: face(n)->refCells()) - removeCell(indexOf(refCell), false); - removeNullFaces(); - } - for (auto& edge : face(n)->edges()) - edge->refFaces().remove([this, n](face_pointer f){ return f == face(n); }); - delete face(n); - if (erase) - faces().remove(n); - } - - darray& cells() - { - return p_cells; - } - - [[nodiscard]] - cell_pointer cell(size_type n) const - { - return p_cells[n]; - } - - template ... Faces> - void addCell(const Faces& ...faces) - { - cells().push(new Cell {static_cast(faces)...}); - for (auto& face : *cells().back()) - face->addRefCell(cells().back()); - } - - void removeNullCells() - { - cells().remove([](cell_pointer cell){ return cell == nullptr; }); - } - - void removeCell(size_type n, bool erase = true, bool cascade = true) - { - static_cast(cascade); - for (auto& face : cell(n)->faces()) - face->refCells().remove([this, n](cell_pointer c){ return c == cell(n); }); - delete cell(n); - if (erase) - cells().remove(n); - } - -}; - +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + + +namespace hpr::mesh +{ + +class Mesh +{ + +public: + using size_type = std::size_t; + using vertex_pointer = Vertex*; + using edge_pointer = Edge*; + using face_pointer = Face*; + using cell_pointer = Cell*; + +protected: + darray p_vertices; + darray p_edges; + darray p_faces; + darray p_cells; + +public: + + Mesh() = default; + + Mesh(const Mesh&) = default; + + ~Mesh() + { + for (auto& v : p_vertices) + delete v; + for (auto& e : p_edges) + delete e; + for (auto& f : p_faces) + delete f; + for (auto& c : p_cells) + delete c; + } + + int indexOf(vertex_pointer v) + { + for (int n = 0; n < vertices().size(); ++n) + if (vertex(n) == v) + return n; + return -1; + } + + int indexOf(edge_pointer e) + { + for (int n = 0; n < edges().size(); ++n) + if (edge(n) == e) + return n; + return -1; + } + + int indexOf(face_pointer e) + { + for (int n = 0; n < faces().size(); ++n) + if (face(n) == e) + return n; + return -1; + } + + int indexOf(cell_pointer e) + { + for (int n = 0; n < cells().size(); ++n) + if (cell(n) == e) + return n; + return -1; + } + + darray& vertices() + { + return p_vertices; + } + + [[nodiscard]] + vertex_pointer vertex(size_type n) const + { + return p_vertices[n]; + } + + void addVertex(const scalar& x, const scalar& y, const scalar& z) + { + p_vertices.push(new Vertex(x, y, z)); + } + + void removeNullVertices() + { + vertices().remove([](vertex_pointer vertex){ return vertex == nullptr; }); + } + + void removeVertex(size_type n, bool erase = true, bool cascade = true) + { + if (cascade) + { + for (auto &refEdge: vertex(n)->refEdges()) + removeEdge(indexOf(refEdge), false); + removeNullEdges(); + } + delete vertex(n); + if (erase) + vertices().remove(n); + } + + darray& edges() + { + return p_edges; + } + + [[nodiscard]] + edge_pointer edge(size_type n) const + { + return p_edges[n]; + } + + void addEdge(vertex_pointer v1, vertex_pointer v2) + { + edges().push(new Edge {v1, v2}); + v1->addRefEdge(edges().back()); + v2->addRefEdge(edges().back()); + } + + void removeNullEdges() + { + edges().remove([](edge_pointer edge){ return edge == nullptr; }); + } + + void removeEdge(size_type n, bool erase = true, bool cascade = true) + { + if (cascade) + { + for (auto &refFace: edge(n)->refFaces()) + removeFace(indexOf(refFace), false); + removeNullFaces(); + } + for (auto& vertex : edge(n)->vertices()) + vertex->refEdges().remove([this, n](edge_pointer e){ return e == edge(n); }); + delete edge(n); + if (erase) + edges().remove(n); + } + + darray& faces() + { + return p_faces; + } + + [[nodiscard]] + face_pointer face(size_type n) const + { + return p_faces[n]; + } + + template ... Edges> + void addFace(const Edges& ...edges) + { + faces().push(new Face {static_cast(edges)...}); + for (auto& edge : *faces().back()) + edge->addRefFace(faces().back()); + } + + void removeNullFaces() + { + faces().remove([](face_pointer face){ return face == nullptr; }); + } + + void removeFace(size_type n, bool erase = true, bool cascade = true) + { + if (cascade) + { + for (auto &refCell: face(n)->refCells()) + removeCell(indexOf(refCell), false); + removeNullFaces(); + } + for (auto& edge : face(n)->edges()) + edge->refFaces().remove([this, n](face_pointer f){ return f == face(n); }); + delete face(n); + if (erase) + faces().remove(n); + } + + darray& cells() + { + return p_cells; + } + + [[nodiscard]] + cell_pointer cell(size_type n) const + { + return p_cells[n]; + } + + template ... Faces> + void addCell(const Faces& ...faces) + { + cells().push(new Cell {static_cast(faces)...}); + for (auto& face : *cells().back()) + face->addRefCell(cells().back()); + } + + void removeNullCells() + { + cells().remove([](cell_pointer cell){ return cell == nullptr; }); + } + + void removeCell(size_type n, bool erase = true, bool cascade = true) + { + static_cast(cascade); + for (auto& face : cell(n)->faces()) + face->refCells().remove([this, n](cell_pointer c){ return c == cell(n); }); + delete cell(n); + if (erase) + cells().remove(n); + } + +}; + } \ No newline at end of file diff --git a/source/hpr/mesh/tests/CMakeLists.txt b/source/hpr/mesh/tests/CMakeLists.txt deleted file mode 100644 index 1644c07..0000000 --- a/source/hpr/mesh/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -file(GLOB tests_cpp "*.cpp") - -add_executable(${PROJECT_NAME}-tests - ${tests_cpp} -) - -target_link_libraries(${PROJECT_NAME}-tests - PUBLIC - hpr::${PROJECT_NAME} - PRIVATE - GTest::gtest_main -) - -gtest_add_tests(TARGET ${PROJECT_NAME}-tests) diff --git a/source/hpr/mesh/tests/hmesh-test.cpp b/source/hpr/mesh/tests/hmesh-test.cpp index 4afaf49..45dfd40 100644 --- a/source/hpr/mesh/tests/hmesh-test.cpp +++ b/source/hpr/mesh/tests/hmesh-test.cpp @@ -1,5 +1,5 @@ #include -#include "../mesh.hpp" +#include TEST(hmeshTest, Mesh) { diff --git a/source/hpr/mesh/vertex.hpp b/source/hpr/mesh/vertex.hpp index e5d3611..ec136ad 100644 --- a/source/hpr/mesh/vertex.hpp +++ b/source/hpr/mesh/vertex.hpp @@ -1,50 +1,50 @@ -#pragma once - -#include "../containers/array.hpp" -#include "../math/scalar/scalar.hpp" -#include "../math/vector.hpp" - - - -namespace hpr::mesh -{ - -class Edge; - -class Vertex : public vec -{ - friend class Mesh; - - using base = vec; - -protected: - darray p_refEdges; - -public: - Vertex() : - base{} - {} - - Vertex(const scalar& x, const scalar& y, const scalar& z) : - base{x, y, z} - {} - - ~Vertex() override - { - for (auto& e: p_refEdges) - e = nullptr; - } - - darray& refEdges() - { - return p_refEdges; - } - - void addRefEdge(Edge* edge) - { - p_refEdges.push(edge); - } - -}; - +#pragma once + +#include +#include +#include + + + +namespace hpr::mesh +{ + +class Edge; + +class Vertex : public vec +{ + friend class Mesh; + + using base = vec; + +protected: + darray p_refEdges; + +public: + Vertex() : + base{} + {} + + Vertex(const scalar& x, const scalar& y, const scalar& z) : + base{x, y, z} + {} + + ~Vertex() override + { + for (auto& e: p_refEdges) + e = nullptr; + } + + darray& refEdges() + { + return p_refEdges; + } + + void addRefEdge(Edge* edge) + { + p_refEdges.push(edge); + } + +}; + } \ No newline at end of file diff --git a/source/hpr/parallel.hpp b/source/hpr/parallel.hpp new file mode 100644 index 0000000..5b51fcc --- /dev/null +++ b/source/hpr/parallel.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/source/hpr/parallel/CMakeLists.txt b/source/hpr/parallel/CMakeLists.txt new file mode 100644 index 0000000..3db2206 --- /dev/null +++ b/source/hpr/parallel/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.16) + +project(parallel + VERSION "${HPR_VERSION}" + LANGUAGES CXX +) + +hpr_add_library(${PROJECT_NAME} INTERFACE) + +hpr_collect_interface(${PROJECT_NAME} + "../parallel.hpp" + "*.hpp" +) + +target_sources(${PROJECT_NAME} + INTERFACE ${${PROJECT_NAME}_HEADERS_INTERFACE} ${HPR_INSTALL_INTERFACE}/${PROJECT_NAME}> +) + +target_link_libraries(${PROJECT_NAME} + INTERFACE + tbb +) + +hpr_install(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}) diff --git a/source/hpr/parallel/parallel.hpp b/source/hpr/parallel/parallel.hpp new file mode 100644 index 0000000..d859456 --- /dev/null +++ b/source/hpr/parallel/parallel.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + + +template diff --git a/source/hpr/window_system.hpp b/source/hpr/window_system.hpp deleted file mode 100644 index 9ee48f5..0000000 --- a/source/hpr/window_system.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - - -#include "window_system/monitor.hpp" -#include "window_system/window_context.hpp" -#include "window_system/window.hpp" -#include "window_system/window_system.hpp" - -#include "window_system/glfw/monitor.hpp" -#include "window_system/glfw/window.hpp" -#include "window_system/glfw/window_system.hpp" diff --git a/source/hpr/window_system/CMakeLists.txt b/source/hpr/window_system/CMakeLists.txt deleted file mode 100644 index 07acbae..0000000 --- a/source/hpr/window_system/CMakeLists.txt +++ /dev/null @@ -1,68 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(window-system - VERSION "${HPR_PROJECT_VERSION}" - LANGUAGES CXX -) - -add_library(${PROJECT_NAME}) -add_library(${CMAKE_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -add_module(${PROJECT_NAME}) - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS - "../window_system.hpp" "*.hpp" "glfw/*.hpp" -) - -foreach(_header_path ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS}) - list(APPEND ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE "$") -endforeach() - -file(GLOB ${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES - "*.cpp" "glfw/*.cpp" -) - -target_sources(${PROJECT_NAME} - INTERFACE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_HEADERS_INTERFACE} - $ - PRIVATE - ${${CMAKE_PROJECT_NAME}_${PROJECT_NAME}_SOURCES} -) - -target_link_libraries(${PROJECT_NAME} - glfw -) - - -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_SKIP - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - EXPORT ${PROJECT_NAME}Targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CMAKE_PROJECT_NAME}-${HPR_PROJECT_VERSION} - NAMESPACE ${CMAKE_PROJECT_NAME}:: -) -install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME} - NAMELINK_ONLY - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) -install( - DIRECTORY ${PROJECT_SOURCE_DIR} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "tests" EXCLUDE -) -install( - FILES ../${PROJECT_NAME}.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_PROJECT_NAME} - COMPONENT devel -) diff --git a/source/hpr/window_system/glfw/monitor.cpp b/source/hpr/window_system/glfw/monitor.cpp deleted file mode 100644 index e87002b..0000000 --- a/source/hpr/window_system/glfw/monitor.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "monitor.hpp" - - -namespace hpr::gpu::glfw -{ - -Monitor::Monitor() : - gpu::Monitor {Provider::GLFW}, - p_instance {nullptr} -{} - -Monitor::~Monitor() = default; - -} \ No newline at end of file diff --git a/source/hpr/window_system/glfw/monitor.hpp b/source/hpr/window_system/glfw/monitor.hpp deleted file mode 100644 index cb70949..0000000 --- a/source/hpr/window_system/glfw/monitor.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "../monitor.hpp" - -#include - - -namespace hpr::gpu::glfw -{ - -class Monitor : public gpu::Monitor -{ - friend class WindowSystem; - friend class Window; - -protected: - - GLFWmonitor* p_instance; - -public: - - Monitor(); - - ~Monitor(); - -}; - -} \ No newline at end of file diff --git a/source/hpr/window_system/glfw/window.cpp b/source/hpr/window_system/glfw/window.cpp deleted file mode 100644 index 156c770..0000000 --- a/source/hpr/window_system/glfw/window.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "window.hpp" -#include "monitor.hpp" - -#include - - -namespace hpr::gpu::glfw -{ - -Window::Window() : - gpu::Window {Provider::GLFW}, - p_instance {nullptr} -{} - -Window::~Window() = default; - -void Window::init(const std::string& title, Style style, int x, int y, int width, int height, gpu::Window* parent, gpu::Monitor* monitor) -{ - if (!checkCompability(parent)) - throw std::invalid_argument("Incompatible window provider passed"); - - gpu::Window::init(title, style, x, y, width, height, parent, monitor); - - glfw::Window* parent_ = dynamic_cast(parent); - glfw::Monitor* monitor_ = dynamic_cast(monitor); - - p_instance = glfwCreateWindow(width, height, title.c_str(), - monitor_ != nullptr ? monitor_->p_instance : nullptr, parent_ != nullptr ? parent_->p_instance : nullptr); - if (p_instance == nullptr) - throw std::runtime_error("Cannot create GLFW window"); - glfwMakeContextCurrent(p_instance); - glfwSetWindowPos(p_instance, x, y); - this->style(style); -} - -void Window::init(const std::string& title, Style style, gpu::Window* parent, gpu::Monitor* monitor) -{ - init(title, style, monitor->originX(), monitor->originY(), monitor->width(), monitor->height(), parent, monitor); -} - -void Window::state(State state) -{ - gpu::Window::state(state); - switch (state) - { - case State::Visible: - glfwShowWindow(p_instance); - break; - case State::Hidden: - glfwHideWindow(p_instance); - break; - case State::Maximized: - glfwMaximizeWindow(p_instance); - break; - case State::Minimized: - glfwIconifyWindow(p_instance); - break; - case State::Closed: - glfwSetWindowShouldClose(p_instance, GLFW_TRUE); - break; - default: - break; - } -} - -void Window::close() -{ - gpu::Window::close(); - glfwDestroyWindow(p_instance); - p_instance = nullptr; -} - -void Window::style(Style style) -{ - gpu::Window::style(style); - if (style == Style::Windowed) - { - glfwSetWindowMonitor(p_instance, nullptr, p_posX, p_posY, p_width, p_height, GLFW_DONT_CARE); - } - else if (style == Style::Fullscreen) - { - glfwSetWindowMonitor(p_instance, glfwGetPrimaryMonitor(), p_posX, p_posY, p_width, p_height, GLFW_DONT_CARE); - } - else if (style == Style::Popup) - { - //throw std::runtime_error("Popup style is not supported"); - } -} - -void Window::swapBuffers() -{ - glfwSwapBuffers(p_instance); -} - -void Window::pollEvents() -{ - glfwPollEvents(); -} - -} \ No newline at end of file diff --git a/source/hpr/window_system/glfw/window.hpp b/source/hpr/window_system/glfw/window.hpp deleted file mode 100644 index 0edc571..0000000 --- a/source/hpr/window_system/glfw/window.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "../window.hpp" - -#include - - -namespace hpr::gpu::glfw -{ - -class Window : public gpu::Window -{ - friend class WindowSystem; - -protected: - - GLFWwindow* p_instance; - -public: - - Window(); - - virtual - ~Window(); - - GLFWwindow* instance() const - { - return p_instance; - } - void init(const std::string& title, Style style, int x, int y, int width, int height, gpu::Window* parent, gpu::Monitor* monitor) override; - - void init(const std::string& title, Style style, gpu::Window* parent, gpu::Monitor* monitor) override; - - void state(State state) override; - - void close() override; - - void style(Style style) override; - - void swapBuffers(); - - void pollEvents(); -}; - -} \ No newline at end of file diff --git a/source/hpr/window_system/glfw/window_system.cpp b/source/hpr/window_system/glfw/window_system.cpp deleted file mode 100644 index 8564a7b..0000000 --- a/source/hpr/window_system/glfw/window_system.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "monitor.hpp" -#include "window.hpp" -#include "window_system.hpp" - -#include - - -namespace hpr::gpu::glfw -{ - -WindowSystem::WindowSystem() : - gpu::WindowSystem {Provider::GLFW} -{ - if (!glfwInit()) - throw std::runtime_error("Cannot initialize GLFW"); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); -} - -WindowSystem::~WindowSystem() -{ - for (auto& window : p_windows) - window->close(); - glfwTerminate(); -} - -gpu::Window* WindowSystem::newWindow() -{ - p_windows.push(new glfw::Window); - - return static_cast(p_windows.back()); -} - -} \ No newline at end of file diff --git a/source/hpr/window_system/glfw/window_system.hpp b/source/hpr/window_system/glfw/window_system.hpp deleted file mode 100644 index c4bfdf5..0000000 --- a/source/hpr/window_system/glfw/window_system.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "../window_system.hpp" - - -namespace hpr::gpu::glfw -{ - -class WindowSystem : public gpu::WindowSystem -{ - -public: - - WindowSystem(); - - ~WindowSystem() override; - - gpu::Window* newWindow() override; - -}; - -} \ No newline at end of file diff --git a/source/hpr/window_system/monitor.cpp b/source/hpr/window_system/monitor.cpp deleted file mode 100644 index 5250d30..0000000 --- a/source/hpr/window_system/monitor.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "monitor.hpp" - - -namespace hpr::gpu -{ - -Monitor::Monitor() : - WindowContext(Provider::Unknown), - p_deviceName {}, - p_originX {0}, - p_originY {0}, - p_width {0}, - p_height {0}, - p_logicalWidth {0}, - p_logicalHeight {0} -{} - -Monitor::Monitor(Provider provider) : - WindowContext(provider), - p_deviceName {}, - p_originX {0}, - p_originY {0}, - p_width {0}, - p_height {0}, - p_logicalWidth {0}, - p_logicalHeight {0} -{} - -Monitor::~Monitor() = default; - -void Monitor::origin(int x, int y) -{ - p_originX = x; - p_originY = y; -} - -void Monitor::size(int width, int height) -{ - p_width = width; - p_height = height; -} - -void Monitor::logicalSize(int width, int height) -{ - p_logicalWidth = width; - p_logicalHeight = height; -} - -void Monitor::deviceName(const std::string &name) -{ - p_deviceName = name; -} - - -} \ No newline at end of file diff --git a/source/hpr/window_system/monitor.hpp b/source/hpr/window_system/monitor.hpp deleted file mode 100644 index ce79cca..0000000 --- a/source/hpr/window_system/monitor.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include "window_context.hpp" - -#include - - -namespace hpr::gpu -{ - -class Monitor : public WindowContext -{ - -protected: - - std::string p_deviceName; - int p_originX; - int p_originY; - int p_width; - int p_height; - int p_logicalWidth; - int p_logicalHeight; - -public: - - Monitor(); - - Monitor(Provider provider); - - virtual - ~Monitor(); - - // Member functions - - void origin(int x, int y); - void size(int width, int height); - void logicalSize(int width, int height); - void deviceName(const std::string& name); - - inline - int width() const - { - return p_width; - } - - inline - int height() const - { - return p_height; - } - - inline - int logicalWidth() const - { - return p_logicalWidth; - } - - inline - int logicalHeight() const - { - return p_logicalHeight; - } - - inline - int originX() const - { - return p_originX; - } - - inline - int originY() const - { - return p_originY; - } - - inline - std::string deviceName() const - { - return p_deviceName; - } - -}; - -} \ No newline at end of file diff --git a/source/hpr/window_system/window.cpp b/source/hpr/window_system/window.cpp deleted file mode 100644 index afe21d6..0000000 --- a/source/hpr/window_system/window.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "window.hpp" - - -namespace hpr::gpu -{ - -Window::Window() : - WindowContext(Provider::Unknown), - p_width {0}, - p_height {0}, - p_posX {0}, - p_posY {0}, - p_title {}, - p_state {State::Hidden}, - p_style {Style::Unknown}, - p_parent {nullptr}, - p_monitor {nullptr}, - p_isActive {true}, - p_isResizing {false} -{} - -Window::Window(Provider provider) : - WindowContext(provider), - p_width {0}, - p_height {0}, - p_posX {0}, - p_posY {0}, - p_title {}, - p_state {State::Hidden}, - p_style {Style::Unknown}, - p_parent {nullptr}, - p_monitor {nullptr}, - p_isActive {true}, - p_isResizing {false} -{} - -Window::~Window() = default; - -void Window::init(const std::string& title, Style style, int x, int y, int width, int height, Window* parent, Monitor* monitor) -{ - p_width = width; - p_height = height; - p_posX = x; - p_posY = y; - - p_title = title; - p_state = State::Hidden; - p_style = style; - - p_parent = parent; - p_monitor = monitor; -} - -void Window::init(const std::string& title, Style style, Window* parent, Monitor* monitor) -{ - init(title, style, monitor->originX(), monitor->originY(), monitor->width(), monitor->height(), parent, monitor); -} - -void Window::state(State state) -{ - p_state = state; -} - -void Window::close() -{ - state(State::Closed); -} - -void Window::restore() -{ - State prevState {p_state}; - init(p_title, p_style, p_posX, p_posY, p_width, p_height, p_parent, p_monitor); - state(prevState); -} - -bool Window::isOpen() const -{ - return p_state != State::Closed; -} - -bool Window::isActive() const -{ - return p_isActive; -} - -void Window::size(int width, int height) -{ - resizeCallback(width, height); -} - -void Window::position(int x, int y) -{ - moveCallback(x, y); -} - -void Window::title(const std::string& title) -{ - p_title = title; -} - -void Window::style(Style style) -{ - p_style = style; -} - -void Window::resizeCallback(int width, int height) -{ - p_width = width; - p_height = height; -} - -void Window::moveCallback(int x, int y) -{ - p_posX = x; - p_posY = y; -} - -} diff --git a/source/hpr/window_system/window.hpp b/source/hpr/window_system/window.hpp deleted file mode 100644 index d52d2b0..0000000 --- a/source/hpr/window_system/window.hpp +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include "window_context.hpp" -#include "monitor.hpp" - - -namespace hpr::gpu -{ - -class Window : public WindowContext -{ - -public: - - enum class State - { - Unknown, - Visible, - Hidden, - Maximized, - Minimized, - Closed, - }; - - enum class Style - { - Unknown, - Windowed, - Fullscreen, - Popup - }; - -protected: - - int p_width; - int p_height; - int p_posX; - int p_posY; - std::string p_title; - - State p_state; - Style p_style; - Window* p_parent; - Monitor* p_monitor; - - bool p_isActive; - bool p_isResizing; - -public: - - Window(); - - Window(Provider provider); - - virtual - ~Window(); - - // Member functions - - virtual - void init(const std::string& title, Style style, int x, int y, int width, int height, Window* parent, Monitor* monitor); - - virtual - void init(const std::string& title, Style style, Window* parent, Monitor* monitor); - - virtual - void state(State state); - - virtual - void close(); - - virtual - void restore(); - - virtual - bool isOpen() const; - - virtual - bool isActive() const; - - virtual - void size(int width, int height); - - virtual - int width() const - { - return p_width; - } - - virtual - int height() const - { - return p_height; - } - - virtual - void position(int x, int y); - - virtual - int posX() const - { - return p_posX; - } - - virtual - int posY() const - { - return p_posY; - } - - virtual - void title(const std::string& title); - - virtual - void style(Style style); - - void resizeCallback(int width, int height); - - void moveCallback(int x, int y); - -}; - -} \ No newline at end of file diff --git a/source/hpr/window_system/window_context.hpp b/source/hpr/window_system/window_context.hpp deleted file mode 100644 index 08f1a0a..0000000 --- a/source/hpr/window_system/window_context.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - - -namespace hpr::gpu -{ - -class WindowContext -{ - -public: - - enum class Provider - { - Unknown, - GLFW - }; - -private: - - Provider p_provider; - -public: - - inline - WindowContext() : - p_provider {Provider::Unknown} - {} - - inline - WindowContext(Provider provider) : - p_provider {provider} - {} - - virtual - ~WindowContext() = default; - - bool checkCompability(const WindowContext* ctx) const - { - return (ctx != nullptr) ? ctx->p_provider == p_provider : true; - } - -}; - -} diff --git a/source/hpr/window_system/window_system.cpp b/source/hpr/window_system/window_system.cpp deleted file mode 100644 index 73281fd..0000000 --- a/source/hpr/window_system/window_system.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "window_system.hpp" -#include "glfw/window_system.hpp" - - -namespace hpr::gpu -{ - -WindowSystem::WindowSystem() : - WindowContext {Provider::Unknown} -{} - -WindowSystem::WindowSystem(Provider provider) : - WindowContext {provider} -{} - -WindowSystem::~WindowSystem() = default; - -WindowSystem* WindowSystem::create(Provider provider) -{ - if (provider == Provider::Unknown) - throw std::invalid_argument("Cannot create window system from 'Unknown' provider"); - - if (provider == Provider::GLFW) - return new glfw::WindowSystem; - else - throw std::invalid_argument("Unsupported window system"); -} - -void WindowSystem::destroy(WindowSystem*& ws) -{ - delete ws; - ws = nullptr; -} - -Window* WindowSystem::window(int index) -{ - return p_windows[index]; -} - -void WindowSystem::closeWindow(Window* window) -{ - window->close(); -} - -void WindowSystem::destroyWindow(Window* window) -{ - for (auto n = 0; n < p_windows.size(); ++n) - if (p_windows[n] == window) - p_windows.remove(n); - window->close(); - window = nullptr; -} - -Monitor* WindowSystem::monitor(int index) -{ - return p_monitors[index]; -} - -void WindowSystem::destroyMonitor(Monitor* monitor) -{ - for (auto n = 0; n < p_monitors.size(); ++n) - if (p_monitors[n] == monitor) - p_monitors.remove(n); -} -} diff --git a/source/hpr/window_system/window_system.hpp b/source/hpr/window_system/window_system.hpp deleted file mode 100644 index 946cbd8..0000000 --- a/source/hpr/window_system/window_system.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "../containers/array.hpp" -#include "window.hpp" -#include "monitor.hpp" - -#include - - -namespace hpr::gpu -{ - -class WindowSystem : public WindowContext -{ - -protected: - - darray p_windows; - darray p_monitors; - -protected: - - WindowSystem(); - - WindowSystem(Provider provider); - - virtual - ~WindowSystem(); - -public: - - // Global functions - - static - WindowSystem* create(Provider provider); - - static - void destroy(WindowSystem*& ws); - - // Window interface - - virtual - Window* newWindow() = 0; - - Window* window(int index); - - void closeWindow(Window* window); - - void destroyWindow(Window* window); - - // Monitor interface - - //virtual - //void newMonitor(Monitor** monitor) = 0; - - Monitor* monitor(int index); - - void destroyMonitor(Monitor* monitor); - -}; - -} \ No newline at end of file