From fd755deeca8ec3a00039dbf1366ac760417ffedc Mon Sep 17 00:00:00 2001 From: patrykcieslak Date: Tue, 19 Jan 2021 16:47:29 +0100 Subject: [PATCH] Implemented a resource manager functionality, which can be used to embed internal resources inside the library binary file, by setting EMBED_RESOURCES option in CMake configuration. Updated documentation. --- CMakeLists.txt | 42 ++++-- Library/include/graphics/OpenGLContent.h | 3 +- Library/src/graphics/GLSLShader.cpp | 65 ++++++--- Library/src/graphics/OpenGLAtmosphere.cpp | 38 +++-- Library/src/graphics/OpenGLContent.cpp | 18 ++- Library/src/graphics/OpenGLOcean.cpp | 24 +++- Library/src/graphics/OpenGLPrinter.cpp | 13 +- ResourceManager/CMakeLists.txt | 45 ++++++ ResourceManager/ResourceHandle.h | 97 +++++++++++++ ResourceManager/embed_resource.cpp | 164 ++++++++++++++++++++++ Tests/ConsoleTest/ConsoleTestManager.cpp | 6 +- docs/building.rst | 4 +- docs/conf.py | 4 +- docs/install.rst | 31 +++- docs/sensors.rst | 41 ++++-- doxygen | 2 +- 16 files changed, 520 insertions(+), 77 deletions(-) create mode 100644 ResourceManager/CMakeLists.txt create mode 100644 ResourceManager/ResourceHandle.h create mode 100644 ResourceManager/embed_resource.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cd5e000..f1482efa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.10) -project(Stonefish VERSION 1.2.0) +project(Stonefish VERSION 1.3.0) +# Automatic configuration configure_file(version.h.in version.h) configure_file(stonefish.pc.in stonefish.pc) @@ -8,10 +9,10 @@ configure_file(stonefish.pc.in stonefish.pc) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() - option(BUILD_TESTS "Build applications testing different features of the Stonefish library" OFF) +option(EMBED_RESOURCES "Embed internal resources in the library executable" OFF) -# Set up CMAKE flags +# Compile flags set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_FLAGS "-DBT_EULER_DEFAULT_ZYX -DBT_USE_DOUBLE_PRECISION") @@ -24,6 +25,17 @@ find_package(OpenGL REQUIRED) find_package(SDL2 REQUIRED) find_package(Freetype REQUIRED) +# Generate C++ code from all resource files (optional) +set(RESOURCES) # This variable stores array of generated resource files +if(EMBED_RESOURCES) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ResourceManager) # Build resource manager + file(GLOB_RECURSE RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/Library/shaders/*.*") # Find resource files + rm_embed_resources(RESOURCES ${RESOURCE_FILES}) # Generate C++ code + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEMBEDDED_RESOURCES -DRM_NO_EXCEPTIONS") + add_definitions(-DSHADER_DIR_PATH=\"Library/shaders/\") #Sets shader path for resources + include_directories(${RESOURCE_MANAGER_INCLUDE_DIRS}) +endif() + # Add include directories include_directories( ${PROJECT_BINARY_DIR} @@ -34,22 +46,26 @@ include_directories( ${FREETYPE_INCLUDE_DIRS} ) -# Build list of library source files +# Find library source files file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Library/src/*.cpp") file(GLOB_RECURSE SOURCES_3RD "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/*.cpp") # Define targets if(BUILD_TESTS) # Create tests and use library locally (has to be disabled when installing system-wide!) - add_library(Stonefish_test SHARED ${SOURCES} ${SOURCES_3RD}) + add_library(Stonefish_test SHARED ${SOURCES} ${SOURCES_3RD} ${RESOURCES}) target_link_libraries(Stonefish_test ${FREETYPE_LIBRARIES} ${OPENGL_LIBRARIES} ${SDL2_LIBRARIES}) - add_definitions(-DSHADER_DIR_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/Library/shaders/\") #Sets shader path for the library + if(NOT EMBED_RESOURCES) + add_definitions(-DSHADER_DIR_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/Library/shaders/\") #Sets shader path for the library + endif() add_subdirectory(Tests) else() # Create shared library to be installed system-wide - add_library(Stonefish SHARED ${SOURCES} ${SOURCES_3RD}) + add_library(Stonefish SHARED ${SOURCES} ${SOURCES_3RD} ${RESOURCES}) target_link_libraries(Stonefish ${FREETYPE_LIBRARIES} ${OPENGL_LIBRARIES} ${SDL2_LIBRARIES}) - add_definitions(-DSHADER_DIR_PATH=\"${CMAKE_INSTALL_PREFIX}/share/Stonefish/shaders/\") #Sets shader path for the library + if(NOT EMBED_RESOURCES) + add_definitions(-DSHADER_DIR_PATH=\"${CMAKE_INSTALL_PREFIX}/share/Stonefish/shaders/\") #Sets shader path for the library + endif() # Install library in the system install( @@ -69,7 +85,9 @@ else() endforeach() #Install other files - install(DIRECTORY Library/shaders/ DESTINATION ${CMAKE_INSTALL_PREFIX}/share/Stonefish/shaders) - install(FILES ${PROJECT_BINARY_DIR}/version.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Stonefish) - install(FILES ${PROJECT_BINARY_DIR}/stonefish.pc DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) -endif() + install(FILES ${PROJECT_BINARY_DIR}/version.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Stonefish) # Version header + install(FILES ${PROJECT_BINARY_DIR}/stonefish.pc DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) # Pkg-config configuration + if(NOT EMBED_RESOURCES) + install(DIRECTORY Library/shaders/ DESTINATION ${CMAKE_INSTALL_PREFIX}/share/Stonefish/shaders) # Resources + endif() +endif() \ No newline at end of file diff --git a/Library/include/graphics/OpenGLContent.h b/Library/include/graphics/OpenGLContent.h index 2272158e..8b765456 100644 --- a/Library/include/graphics/OpenGLContent.h +++ b/Library/include/graphics/OpenGLContent.h @@ -330,9 +330,10 @@ namespace sf \param filename the path to the texture file \param hasAlphaChannel a flag to indicate if the texture has transparency \param anisotropy defines maximum anisotropic filtering + \param internal a flag to indicate if the texture is an internal resource \return the id of the loaded texture */ - static GLuint LoadTexture(std::string filename, bool hasAlphaChannel = false, GLfloat anisotropy = 0.f); + static GLuint LoadTexture(std::string filename, bool hasAlphaChannel = false, GLfloat anisotropy = 0.f, bool internal = false); //! A static method to load an internal texture. /*! diff --git a/Library/src/graphics/GLSLShader.cpp b/Library/src/graphics/GLSLShader.cpp index b0673aa2..a1e2758f 100644 --- a/Library/src/graphics/GLSLShader.cpp +++ b/Library/src/graphics/GLSLShader.cpp @@ -29,6 +29,10 @@ #include "core/SimulationApp.h" #include "graphics/OpenGLState.h" #include "utils/SystemUtil.hpp" +#ifdef EMBEDDED_RESOURCES +#include +#include "ResourceHandle.h" +#endif namespace sf { @@ -438,24 +442,34 @@ void GLSLShader::Verbose() GLuint GLSLShader::LoadShader(GLenum shaderType, const std::string& filename, const std::string& header, GLint *shaderCompiled) { GLuint shader = 0; - - std::string basePath = GetShaderPath(); - std::string sourcePath = basePath + filename; - + std::string sourcePath = GetShaderPath() + filename; +#ifdef EMBEDDED_RESOURCES + ResourceHandle rh(sourcePath); + if(!rh.isValid()) + { + cCritical("Shader resource not found: %s", sourcePath.c_str()); + return 0; + } + std::istringstream sourceString(rh.string()); + std::istream& sourceBuf(sourceString); +#else std::ifstream sourceFile(sourcePath); - if(!sourceFile.is_open()) + if(!sourceFile.is_open()) + { cCritical("Shader file not found: %s", sourcePath.c_str()); - - std::string source = header + "\n"; - std::string line; - + return 0; + } + std::istream& sourceBuf(sourceFile); +#endif #ifdef DEBUG if(verbose) cInfo("Loading shader from: %s", sourcePath.c_str()); #endif - while(!sourceFile.eof()) + std::string source = header + "\n"; + std::string line; + while(!sourceBuf.eof()) { - std::getline(sourceFile, line); + std::getline(sourceBuf, line); if(line.find("#inject") == std::string::npos) source.append(line + "\n"); @@ -466,30 +480,46 @@ GLuint GLSLShader::LoadShader(GLenum shaderType, const std::string& filename, co if(pos1 > 0 && pos2 > pos1) { - std::string injectedPath = basePath + line.substr(pos1+1, pos2-pos1-1); + std::string injectedPath = GetShaderPath() + line.substr(pos1+1, pos2-pos1-1); +#ifdef EMBEDDED_RESOURCES + ResourceHandle rh2(injectedPath); + if(!rh2.isValid()) + { + cCritical("Shader include resource not found: %s", injectedPath.c_str()); + return 0; + } + std::istringstream injectedString(rh2.string()); + std::istream& injectedBuf(injectedString); +#else + std::ifstream injectedFile(injectedPath); if(!injectedFile.is_open()) { sourceFile.close(); cCritical("Shader include file not found: %s", injectedPath.c_str()); + return 0; } + std::istream& injectedBuf(injectedFile); +#endif #ifdef DEBUG if(verbose) cInfo("--> Injecting source from: %s", injectedPath.c_str()); #endif - while(!injectedFile.eof()) + while(!injectedBuf.eof()) { - std::getline(injectedFile, line); + std::getline(injectedBuf, line); source.append(line + "\n"); } +#ifndef EMBEDDED_RESOURCES injectedFile.close(); +#endif } } } +#ifndef EMBEDDED_RESOURCES sourceFile.close(); - +#endif const char* shaderSource = source.c_str(); - if(shaderSource != NULL) { shader = glCreateShader(shaderType); @@ -497,7 +527,10 @@ GLuint GLSLShader::LoadShader(GLenum shaderType, const std::string& filename, co glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, shaderCompiled); if(*shaderCompiled == 0) + { cError("Failed to compile shader: %s", shaderSource); + shader = 0; + } #ifdef DEBUG GLint infoLogLength = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); diff --git a/Library/src/graphics/OpenGLAtmosphere.cpp b/Library/src/graphics/OpenGLAtmosphere.cpp index b72f6408..d961f9cd 100644 --- a/Library/src/graphics/OpenGLAtmosphere.cpp +++ b/Library/src/graphics/OpenGLAtmosphere.cpp @@ -34,6 +34,10 @@ #include "graphics/OpenGLCamera.h" #include "graphics/OpenGLContent.h" #include "utils/SystemUtil.hpp" +#ifdef EMBEDDED_RESOURCES +#include +#include "ResourceHandle.h" +#endif namespace sf { @@ -243,16 +247,22 @@ void OpenGLAtmosphere::LoadAtmosphereData(const std::string& filename) glm::uvec3 scatteringSize; GLfloat lengthUnitInMeters; GLfloat bottomRadius; - +#ifdef EMBEDDED_RESOURCES + ResourceHandle rh(filename); + std::istringstream dataString(rh.string()); + std::istream& data(dataString); +#else std::ifstream dataFile(filename, std::ios::binary | std::ios::in); - dataFile.read((char*)&transmittanceSize, sizeof(transmittanceSize)); - dataFile.read((char*)&irradianceSize, sizeof(irradianceSize)); - dataFile.read((char*)&scatteringSize, sizeof(scatteringSize)); - dataFile.read((char*)&lengthUnitInMeters, sizeof(lengthUnitInMeters)); - dataFile.read((char*)&bottomRadius, sizeof(bottomRadius)); - dataFile.read((char*)&sunSkyUBOData.whitePoint, sizeof(sunSkyUBOData.whitePoint)); - dataFile.read((char*)&nPrecomputedWavelengths, sizeof(nPrecomputedWavelengths)); - dataFile.read((char*)&nScatteringOrders, sizeof(nScatteringOrders)); + std::istream& data(dataFile); +#endif + data.read((char*)&transmittanceSize, sizeof(transmittanceSize)); + data.read((char*)&irradianceSize, sizeof(irradianceSize)); + data.read((char*)&scatteringSize, sizeof(scatteringSize)); + data.read((char*)&lengthUnitInMeters, sizeof(lengthUnitInMeters)); + data.read((char*)&bottomRadius, sizeof(bottomRadius)); + data.read((char*)&sunSkyUBOData.whitePoint, sizeof(sunSkyUBOData.whitePoint)); + data.read((char*)&nPrecomputedWavelengths, sizeof(nPrecomputedWavelengths)); + data.read((char*)&nScatteringOrders, sizeof(nScatteringOrders)); sunSkyUBOData.atmLengthUnitInMeters = lengthUnitInMeters; sunSkyUBOData.planetRadiusInUnits = bottomRadius/lengthUnitInMeters; @@ -260,20 +270,20 @@ void OpenGLAtmosphere::LoadAtmosphereData(const std::string& filename) //Load atmosphere textures glm::vec4* pixels; pixels = new glm::vec4[transmittanceSize.x * transmittanceSize.y]; - dataFile.read((char*)pixels, sizeof(glm::vec4) * transmittanceSize.x * transmittanceSize.y); + data.read((char*)pixels, sizeof(glm::vec4) * transmittanceSize.x * transmittanceSize.y); textures[AtmosphereTextures::TRANSMITTANCE] = OpenGLContent::GenerateTexture(GL_TEXTURE_2D, transmittanceSize, GL_RGBA32F, GL_RGBA, GL_FLOAT, pixels, FilteringMode::BILINEAR, false); delete [] pixels; pixels = new glm::vec4[irradianceSize.x * irradianceSize.y]; - dataFile.read((char*)pixels, sizeof(glm::vec4) * irradianceSize.x * irradianceSize.y); + data.read((char*)pixels, sizeof(glm::vec4) * irradianceSize.x * irradianceSize.y); textures[AtmosphereTextures::IRRADIANCE] = OpenGLContent::GenerateTexture(GL_TEXTURE_2D, irradianceSize, GL_RGBA32F, GL_RGBA, GL_FLOAT, pixels, FilteringMode::BILINEAR, false); delete [] pixels; pixels = new glm::vec4[scatteringSize.x * scatteringSize.y * scatteringSize.z]; - dataFile.read((char*)pixels, sizeof(glm::vec4) * scatteringSize.x * scatteringSize.y * scatteringSize.z); + data.read((char*)pixels, sizeof(glm::vec4) * scatteringSize.x * scatteringSize.y * scatteringSize.z); textures[AtmosphereTextures::SCATTERING] = OpenGLContent::GenerateTexture(GL_TEXTURE_3D, scatteringSize, GL_RGBA16F, GL_RGBA, GL_FLOAT, pixels, FilteringMode::BILINEAR, false); delete [] pixels; - +#ifndef EMBEDDED_RESOURCES dataFile.close(); - +#endif cInfo("Loaded precomputed atmosphere model (%u wavelengths, %u scattering orders)", nPrecomputedWavelengths, nScatteringOrders); } diff --git a/Library/src/graphics/OpenGLContent.cpp b/Library/src/graphics/OpenGLContent.cpp index f7becf71..e8b571b5 100644 --- a/Library/src/graphics/OpenGLContent.cpp +++ b/Library/src/graphics/OpenGLContent.cpp @@ -43,6 +43,9 @@ #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" +#ifdef EMBEDDED_RESOURCES +#include "ResourceHandle.h" +#endif #define clamp(x,min,max) (x > max ? max : (x < min ? min : x)) @@ -1150,7 +1153,7 @@ const Look& OpenGLContent::getLook(size_t id) } //Static methods -GLuint OpenGLContent::LoadTexture(std::string filename, bool hasAlphaChannel, GLfloat anisotropy) +GLuint OpenGLContent::LoadTexture(std::string filename, bool hasAlphaChannel, GLfloat anisotropy, bool internal) { int width, height, channels; int reqChannels = hasAlphaChannel ? 4 : 3; @@ -1158,7 +1161,18 @@ GLuint OpenGLContent::LoadTexture(std::string filename, bool hasAlphaChannel, GL // Allocate image; fail out on error stbi_set_flip_vertically_on_load(true); +#ifdef EMBEDDED_RESOURCES + unsigned char* dataBuffer; + if(internal) + { + ResourceHandle rh(filename); + dataBuffer = stbi_load_from_memory(rh.data(), rh.size(), &width, &height, &channels, reqChannels); + } + else + dataBuffer = stbi_load(filename.c_str(), &width, &height, &channels, reqChannels); +#else unsigned char* dataBuffer = stbi_load(filename.c_str(), &width, &height, &channels, reqChannels); +#endif if(dataBuffer == NULL) { cError("Failed to load texture from: %s", filename.c_str()); @@ -1191,7 +1205,7 @@ GLuint OpenGLContent::LoadTexture(std::string filename, bool hasAlphaChannel, GL GLuint OpenGLContent::LoadInternalTexture(std::string filename, bool hasAlphaChannel, GLfloat anisotropy) { - return LoadTexture(GetShaderPath() + filename, hasAlphaChannel, anisotropy); + return LoadTexture(GetShaderPath() + filename, hasAlphaChannel, anisotropy, true); } GLuint OpenGLContent::GenerateTexture(GLenum target, glm::uvec3 dimensions, GLenum internalFormat, GLenum format, GLenum type, const void* data, diff --git a/Library/src/graphics/OpenGLOcean.cpp b/Library/src/graphics/OpenGLOcean.cpp index b440d98a..e94a3113 100644 --- a/Library/src/graphics/OpenGLOcean.cpp +++ b/Library/src/graphics/OpenGLOcean.cpp @@ -42,6 +42,10 @@ #include "entities/forcefields/Uniform.h" #include "entities/forcefields/Jet.h" #include "entities/forcefields/Pipe.h" +#ifdef EMBEDDED_RESOURCES +#include +#include "ResourceHandle.h" +#endif namespace sf { @@ -308,13 +312,21 @@ OpenGLOcean::OpenGLOcean(GLfloat size) glBindBuffer(GL_UNIFORM_BUFFER, 0); //Load absorption coefficient table +#ifdef EMBEDDED_RESOURCES + ResourceHandle rh(GetShaderPath() + "jerlov.dat"); + if(!rh.isValid()) + cCritical("Ocean water data could not be loaded!"); + std::istringstream dataStream(rh.string()); + dataStream.read((char*)absorption, sizeof(absorption)); + dataStream.read((char*)scattering, sizeof(scattering)); +#else std::ifstream dataFile(GetShaderPath() + "jerlov.dat", std::ios::in | std::ios::binary); - if(dataFile.is_open()) - { - dataFile.read((char*)absorption, sizeof(absorption)); - dataFile.read((char*)scattering, sizeof(scattering)); - dataFile.close(); - } + if(!dataFile.is_open()) + cCritical("Ocean water data could not be loaded!"); + dataFile.read((char*)absorption, sizeof(absorption)); + dataFile.read((char*)scattering, sizeof(scattering)); + dataFile.close(); +#endif } OpenGLOcean::~OpenGLOcean() diff --git a/Library/src/graphics/OpenGLPrinter.cpp b/Library/src/graphics/OpenGLPrinter.cpp index ae97e68e..80c08b3d 100644 --- a/Library/src/graphics/OpenGLPrinter.cpp +++ b/Library/src/graphics/OpenGLPrinter.cpp @@ -28,6 +28,9 @@ #include "graphics/OpenGLState.h" #include "graphics/GLSLShader.h" #include +#ifdef EMBEDDED_RESOURCES +#include "ResourceHandle.h" +#endif namespace sf { @@ -57,9 +60,15 @@ OpenGLPrinter::OpenGLPrinter(const std::string& fontPath, GLuint size) printf("Freetype: Could not init library!\n"); else { - if((error = FT_New_Face(ft, fontPath.c_str(), 0, &face))) +#ifdef EMBEDDED_RESOURCES + ResourceHandle rh(fontPath); + error = FT_New_Memory_Face(ft, rh.data(), rh.size(), 0, &face); +#else + error = FT_New_Face(ft, fontPath.c_str(), 0, &face); +#endif + if(error) { - printf("Freetype: Could not open font from file: %s!\n", fontPath.c_str()); + printf("Freetype: Could not load font from: %s!\n", fontPath.c_str()); FT_Done_FreeType(ft); } } diff --git a/ResourceManager/CMakeLists.txt b/ResourceManager/CMakeLists.txt new file mode 100644 index 00000000..098eb42f --- /dev/null +++ b/ResourceManager/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (c) 2018 Johnny Borov . Released under MIT License. + +add_executable(embed_resource embed_resource.cpp) + +set(RESOURCE_MANAGER_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE) + +# this file contains defenition for ResourceHandle constructor +set(RESOURCE_MANAGER_RESOURCES_CONFIG_FILE "RM_generated_files/__resources__config.cpp" PARENT_SCOPE) + +function(rm_embed_resources output_resources_list) + if(${ARGC} EQUAL 1) + message(FATAL_ERROR "ResourceManager::rm_embed_resources: No resources provided") + else() + set(input_files_list) + set(output_files_list) + foreach(input_file_name ${ARGN}) + file(RELATIVE_PATH relative_to_binary_input_file_name "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${input_file_name}") + set(output_file_name "RM_generated_files/${input_file_name}.cpp") + get_filename_component(output_file_dir ${output_file_name} DIRECTORY) + + # this runs only if input_file_name file changed since last time or output_file_name doesnt exist + add_custom_command( + OUTPUT "${output_file_name}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${output_file_dir}" + COMMAND embed_resource "-data" "${input_file_name}" "${relative_to_binary_input_file_name}" "${output_file_name}" + DEPENDS "${input_file_name}" + VERBATIM) + + list(APPEND input_files_list "${input_file_name}") + list(APPEND output_files_list "${output_file_name}") + endforeach() + + # this runs only if one or more output_file_name file from the loop before changed since last time + # or if RESOURCE_MANAGER_RESOURCES_CONFIG_FILE doesnt exist + # there's no need to call cmake -E make_directory because its already created by this time + add_custom_command( + OUTPUT "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}" + COMMAND embed_resource "-config" "${input_files_list}" "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}" + DEPENDS ${output_files_list} + VERBATIM) + + list(APPEND output_files_list "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}") + set(${output_resources_list} ${output_files_list} PARENT_SCOPE) + endif() +endfunction() \ No newline at end of file diff --git a/ResourceManager/ResourceHandle.h b/ResourceManager/ResourceHandle.h new file mode 100644 index 00000000..7a1c2b3f --- /dev/null +++ b/ResourceManager/ResourceHandle.h @@ -0,0 +1,97 @@ +/* +Copyright (c) 2018 Johnny Borov + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef RM_RESOURCE_HANDLE_H +#define RM_RESOURCE_HANDLE_H + +#include +#include +#include + +// This exception type is thrown if ResourceHandle constructor gets invalid resource name. +// Compile with -DRM_NO_EXCEPTIONS to disable throwing on invalid resource name. +class ResourceNotFound : public std::exception { +public: + ResourceNotFound(std::string resource_name) : m_message{"ResourceManager: Resource not found: " + resource_name} {} + virtual const char* what() const noexcept { return m_message.c_str(); } + +private: + const std::string m_message; +}; + + +// This class holds a pointer to the beginning of binary data for the requested resource +// and the length/size (in bytes) of this data. (length and size are the same thing). +// ------------------------------------------------------------------------------------------- +// If constructor is called with an invalid resource name ResourceNotFound exception is thrown +// Compile with -DRM_NO_EXCEPTIONS to disable throwing on invalid resource name. +// ------------------------------------------------------------------------------------------- +// If exceptions are disabled and constructor is called with an invalid resource name +// then the pointer to the data equials nullptr and length/size equals 0. +// In this case you can use isValid() to determine whether construction was successful or not. +// =========================================================================================== +// Avaliable functions: +// -- const bool isValid() const noexcept: +// # returns true if construction was successful, false otherwise. +// # +// -- const unsigned char* const data() const noexcept: +// # returns the pointer to the beginning of binary data or nullptr if construction failed. +// # The data is null-terminated in the end. +// # +// -- const char* const c_str() const noexcept: +// # returns the pointer to the beginning of binary data or nullptr if construction failed. +// # The data is null-terminated in the end. +// # +// -- std::string string() const: +// # returns std::string based on the binary data with same length as the data. +// # If there are zeroes in the middle of the data string will contain them anyway. +// # +// -- const size_t size() const noexcept: +// # returns size of the data in bytes or 0 if construction failed. +// # Null-terminator is not taken into account. +// # +// -- const size_t length() const noexcept: +// # same as size(). +// # +// =========================================================================================== +class ResourceHandle { +public: + ResourceHandle(std::string resource_name); + + const bool isValid() const noexcept { if (m_data_start) return true; else return false; } + + const size_t size() const noexcept { return m_data_len; } + const size_t length() const noexcept { return m_data_len; } + + const unsigned char* const data() const noexcept { return m_data_start; } + + const char* const c_str() const noexcept { return reinterpret_cast(m_data_start); } + std::string string() const { return std::string(reinterpret_cast(m_data_start), m_data_len); } + +private: + const unsigned char* m_data_start; + size_t m_data_len; +}; + +#endif // RM_RESOURCE_HANDLE_H \ No newline at end of file diff --git a/ResourceManager/embed_resource.cpp b/ResourceManager/embed_resource.cpp new file mode 100644 index 00000000..72f4f24e --- /dev/null +++ b/ResourceManager/embed_resource.cpp @@ -0,0 +1,164 @@ +/* +Copyright (c) 2018 Johnny Borov + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include + +void generateResourceDataSourceFile(char* resource_name, char* resource_file_name, char* output_file_name); +void generateResourcesConfigSourceFile(char* resource_names_list, char* config_file_name); +std::string modifyFileName(std::string file_name); + + +int main(int argc, char* argv[]) { + // working directory = CMAKE_BINARY_DIR when called from cmake custom command + if (std::string(argv[1]) == "-data") { + if (argc == 5) + generateResourceDataSourceFile(argv[2], argv[3], argv[4]); + else + return -1; + } else if (std::string(argv[1]) == "-config") { + if (argc == 4) + generateResourcesConfigSourceFile(argv[2], argv[3]); + else + return -1; + } + + return 0; +} + + +// generate a cpp file (e.g. resources/res.txt.cpp) containing global extern array of const unsigned char +// of binary data read from the requested input file (CMAKE_SOURCE_DIR/resources/res.txt) plus null-terminator +// in the end of data and a global extern const size_t size of this data in bytes (without null-terminator) +void generateResourceDataSourceFile(char* resource_name, char* resource_file_name, char* output_file_name) { + std::string name{ resource_name }; // e.g. "resources/res.txt" + std::string modified_name = modifyFileName(name); // becomes "resources__slash__res__dot__txt" + + std::ifstream ifs{ resource_file_name, std::ios::binary }; // e.g. reads from CMAKE_SOURCE_DIR/resources/res.txt + std::ofstream ofs{ output_file_name }; // e.g. writes to resources/res.txt.cpp + + ofs << "#include \n"; + ofs << "extern const unsigned char _resource_" << modified_name << "_data[] = {\n"; + + int line_count = 0; + char c; + while (ifs.get(c)) { + ofs << "0x" << std::hex << (c & 0xff) << ", "; + + if (++line_count == 10) { + ofs << '\n'; + line_count = 0; + } + } + + ofs << "\'\\0\'"; // null-terminator in case data is going to be interprited as a c string + ofs << "};\n"; + // -1 excludes null-terminator + ofs << "extern const size_t _resource_" << modified_name << "_len = sizeof(_resource_" << modified_name << "_data) - 1;\n"; +} + + +// generate a cpp file (e.g. __resources__config.cpp) that defines ResourceHandle constructor. +// The defenition contains the hardcoded mapping of original resource names (e.g. "resources/res.txt") +// to their corresponding global extern variables names. Thus it returns a handle which contains pointer to +// the beginning of global extern array of const unsigned char and its size (see -data option description) +void generateResourcesConfigSourceFile(char* resource_names_list, char* config_file_name) { + std::ofstream ofs(config_file_name); // e.g. writes to __resources__config.cpp + + ofs << + "#include \"ResourceHandle.h\"\n" + "\n" + "ResourceHandle::ResourceHandle(std::string resource_name) {\n" + " "; + + + std::string resource_names{resource_names_list}; // e.g. "res.txt;res2.txt;resources/res.txt" + size_t length = resource_names.length(); + size_t start_pos = 0; + do { + size_t end_pos = resource_names.find_first_of(';', start_pos); + if (end_pos == std::string::npos) + end_pos = length; + + // e.g. "res.txt;res2.txt;resources/res.txt" -> "res.txt" -> "res__dot__txt" + std::string name = resource_names.substr(start_pos, end_pos - start_pos); + std::string modified_name = modifyFileName(name); + + ofs << + "if (resource_name == \"" << name << "\") {\n" + " extern const unsigned char _resource_" << modified_name << "_data[];\n" + " extern const size_t _resource_" << modified_name << "_len;\n" + " m_data_start = _resource_" << modified_name << "_data;\n" + " m_data_len = _resource_" << modified_name << "_len;\n" + " } else "; + + start_pos = end_pos + 1; // e.g. start next read from res2... position in "res.txt;res2.txt;resources/res.txt" + } while (start_pos < length); + + + ofs << + "{\n" + "#ifdef RM_NO_EXCEPTIONS\n" + " m_data_start = nullptr;\n" + " m_data_len = 0;\n" + "#else\n" + " throw ResourceNotFound{resource_name};\n" + "#endif\n" + " }\n" + "}\n"; +} + + +// replace symbols that cant be used in c++ identeficator name +// e.g. "resources/res.txt" -> "resources__slash__res__dot__txt" +std::string modifyFileName(std::string file_name) { + size_t search_from_pos = 0; + size_t replace_from_pos; + while ((replace_from_pos = file_name.find_first_of(".- /\\", search_from_pos)) != std::string::npos) { + switch (file_name[replace_from_pos]) { + case '.': + file_name.replace(replace_from_pos, 1, "__dot__"); + search_from_pos = replace_from_pos + 7; // shift len(__dot__) = 5 symbols + break; + case '-': + file_name.replace(replace_from_pos, 1, "__dash__"); + search_from_pos = replace_from_pos + 8; + break; + case ' ': + file_name.replace(replace_from_pos, 1, "__space__"); + search_from_pos = replace_from_pos + 9; + break; + case '/': + file_name.replace(replace_from_pos, 1, "__slash__"); + search_from_pos = replace_from_pos + 9; + break; + case '\\': + file_name.replace(replace_from_pos, 1, "__bslash__"); + search_from_pos = replace_from_pos + 10; + break; + } + } + + return file_name; +} \ No newline at end of file diff --git a/Tests/ConsoleTest/ConsoleTestManager.cpp b/Tests/ConsoleTest/ConsoleTestManager.cpp index 22cb86f6..a2cd1dd0 100644 --- a/Tests/ConsoleTest/ConsoleTestManager.cpp +++ b/Tests/ConsoleTest/ConsoleTestManager.cpp @@ -44,9 +44,9 @@ void ConsoleTestManager::BuildScenario() sf::ScenarioParser parser(this); bool success = parser.Parse(sf::GetDataPath() + "console_test.scn"); if(success) - sf::cInfo("Scenario description parsed successfully."); + cInfo("Scenario description parsed successfully."); else - sf::cError("Errors detected when parsing scenario description!"); + cError("Errors detected when parsing scenario description!"); #else //Create materials CreateMaterial("Rock", sf::UnitSystem::Density(sf::CGS, sf::MKS, 3.0), 0.8); @@ -77,5 +77,5 @@ void ConsoleTestManager::BuildScenario() void ConsoleTestManager::SimulationStepCompleted(sf::Scalar timeStep) { - sf::cInfo("Simulation time: %1.3lf", getSimulationTime()); + cInfo("Simulation time: %1.3lf", getSimulationTime()); } diff --git a/docs/building.rst b/docs/building.rst index 63a71986..26224cc6 100644 --- a/docs/building.rst +++ b/docs/building.rst @@ -68,7 +68,7 @@ Following the steps above should result in 3 new files: *main.cpp*, *MySimulatio #include "MySimulationManager.h" #include - #include + #include MySimulationManager::MySimulationManager(sf::Scalar stepsPerSecond) : SimulationManager(stepsPerSecond) { @@ -92,7 +92,7 @@ Following the steps above should result in 3 new files: *main.cpp*, *MySimulatio AddStaticEntity(plane, sf::I4()); //Create object - sf::Sphere* sph = new sf::Sphere("Sphere", 0.1, sf::I4(), "Aluminium", sf::BodyPhysicsType::SURFACE_BODY, "red"); + sf::Sphere* sph = new sf::Sphere("Sphere", 0.1, sf::I4(), "Aluminium", sf::BodyPhysicsType::SURFACE, "red"); AddSolidEntity(sph, sf::Transform(sf::IQ(), sf::Vector3(0.0,0.0,-1.0))); } diff --git a/docs/conf.py b/docs/conf.py index 4f6064c5..6739a915 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,9 +25,9 @@ author = u'Patryk Cieślak' # The short X.Y version -version = u'1.2' +version = u'1.3' # The full version, including alpha/beta/rc tags -release = u'1.2.0' +release = u'1.3.0' # -- General configuration --------------------------------------------------- diff --git a/docs/install.rst b/docs/install.rst index a99d7014..5a95ed9f 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -5,9 +5,7 @@ Installation Requirements ============ -The *Stonefish* library requires a modern multi-core processor to run the physics calculations in realtime. - -A discrete graphics card with the support for **OpenGL 4.3 or higher** is required to run graphical simulations. +The *Stonefish* library requires a **modern multi-core processor** to run the physics calculations in realtime. A **powerful GPU** with the support for **OpenGL 4.3 or higher** is required to run graphical simulations. If the GPU is not fulfilling the requirements it is still possible to run simulation in console mode, with limitted functionality. Dependencies ============ @@ -20,14 +18,31 @@ The following dependencies have to be installed prior to building the library: .. note:: - SDL2 library may need a small fix to the cmake configuration file, to avoid build errors. Remove a space after ``-lSDL2`` in ``/usr/lib/x86_64-linux-gnu/cmake/SDL2/sdl2-config.cmake``. + SDL2 library may need a small fix to the CMake configuration file, to avoid build errors. Remove a space after ``-lSDL2`` in ``/usr/lib/x86_64-linux-gnu/cmake/SDL2/sdl2-config.cmake``. * `Freetype `_ (libfreetype6-dev) Building ======== -The following steps are needed to build the library: +The easiest way to build the library is to let `CMake `_ do its job at configuring the build process. +The standard build configuration includes building the dynamic library for system-wide use and creating +the *install* target for make. The installation includes the library binary, header files and internal resources. +It is possible to define the install location by modifying the standard variable ``CMAKE_INSTALL_PREFIX``, through the command line or the *cmake-gui* tool. + +There are two special build options defined for CMake: + +1. ``BUILD_TESTS`` + * build dynamic library for local use, without an option for system-wide installation + * set path of internal resources to the source code location + * build tests/examples of simulators +2. ``EMBED_RESOURCES`` + * generate C++ code from all internal resources + * compile the resources and embed them inside the library binary file + * no need to install resources as files in the shared system location + * useful for a binary release + +The following terminal commands are necessary to clone, build and install the library with a standard configuration (*X* number of cores to use): .. code-block:: console @@ -42,7 +57,7 @@ The following steps are needed to build the library: Generating code documentation ============================= -The following steps are needed to generate and open the documentation of the library code. +The following steps are needed to generate and open the documentation of the library code. It is assumed that `Doxygen `_ is already installed in the system. 1. Go to "stonefish" directory. 2. ``$ doxygen doxygen`` @@ -61,4 +76,6 @@ The following 3rd party code is included in the source of the library and will b * `stb_image_write `_ (C library) -* OpenGL 4.6 functions loader generated with `GLAD2 `_ \ No newline at end of file +* OpenGL 4.6 functions loader generated with `GLAD2 `_ + +* `ResourceManager `_ (C++ class and tool) \ No newline at end of file diff --git a/docs/sensors.rst b/docs/sensors.rst index 8b4f9caa..1da444a8 100644 --- a/docs/sensors.rst +++ b/docs/sensors.rst @@ -135,15 +135,38 @@ The link sensors measure motion related or environment related quantities. They +Accelerometer +------------- + +The accelerometer measures the linear acceleration of the link, along three perpendicular axes. The linear acceleration measurement range, as well as the standard deviation of the measurements, can be optionally defined for each axis. + +.. code-block:: xml + + + + + + + + + +.. code-block:: cpp + + #include + sf::Accelerometer* acc = new sf::Accelerometer("Acc", 10.0, 1); + acc->setRange(sf::Vector3(1000.0, 1000.0, 2000.0)); + acc->setNoise(sf::Vector3(0.1, 0.1, 0.1)); + robot->AddLinkSensor(acc, "Link1", sf::Transform(sf::Quaternion(0.0, 0.0, 0.0), sf::Vector3(0.1, 0.0, 0.0)); + Gyroscope --------- -The gyroscope measures the angular velocities of the link, around three perpendicular axes. The angular velocity measurement range, as well as the standard deviation of the measurements and the measurement bias, can be optionally defined. +The gyroscope measures the angular velocities of the link, around three perpendicular axes. The angular velocity measurement range, as well as the standard deviation of the measurements and the measurement bias, can be optionally defined, for each axis. .. code-block:: xml - + @@ -154,20 +177,20 @@ The gyroscope measures the angular velocities of the link, around three perpendi #include sf::Gyroscope* gyro = new sf::Gyroscope("Gyro", 10.0, 1); - gyro->setRange(100.0); - gyro->setNoise(0.05, 0.003); + gyro->setRange(sf::Vector3(100.0, 100.0, 200.0)); + gyro->setNoise(sf::Vector3(0.05, 0.05, 0.05), sf::Vector3(0.003, 0.003, 0.003)); robot->AddLinkSensor(gyro, "Link1", sf::Transform(sf::Quaternion(0.0, 0.0, 0.0), sf::Vector3(0.1, 0.0, 0.0)); IMU --- -The inertial measurement unit (IMU) measures the orientation and the angular velocities of the link. The angular velocity measurement range and the standard deviation of angle and angular velocity measurements can be optionally defined. All characteristics can be defined separately for each of the 3 axes or as common value for all of them. Additionally, the IMU yaw angle drift rate can be specified. +The inertial measurement unit (IMU) measures the orientation, angular velocities, and linear accelerations of the link. The angular velocity and linear acceleration measurement ranges and the standard deviation of angle, angular velocity, and linear acceleration measurements can be optionally defined, for each axis. Additionally, the IMU yaw angle drift rate can be specified. .. code-block:: xml - - + + @@ -177,8 +200,8 @@ The inertial measurement unit (IMU) measures the orientation and the angular vel #include sf::IMU* imu = new sf::IMU("IMU", 10.0, 1); - imu->setRange(sf::Vector3(10.0, 10.0, 5.0)); - imu->setNoise(sf::Vector3(0.1, 0.1, 0.5), sf::Vector3(0.05, 0.05, 0.05), 0.001); + imu->setRange(sf::Vector3(10.0, 10.0, 5.0), sf::Vector3(10.0, 10.0, 10.0)); + imu->setNoise(sf::Vector3(0.1, 0.1, 0.5), sf::Vector3(0.05, 0.05, 0.05), 0.001, sf::Vector3(0.1, 0.1, 0.1)); robot->AddLinkSensor(imu, "Link1", sf::Transform(sf::Quaternion(0.0, 0.0, 0.0), sf::Vector3(0.1, 0.0, 0.0)); Odometry diff --git a/doxygen b/doxygen index 0ad62a4b..f3e23967 100644 --- a/doxygen +++ b/doxygen @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = Stonefish -PROJECT_NUMBER = 1.2 +PROJECT_NUMBER = 1.3 PROJECT_BRIEF = "An advanced simulation tool designed for marine robotics" PROJECT_LOGO = ./Library/shaders/logo_64.png OUTPUT_DIRECTORY = ./docs