diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index b8c5345dc..56b36873a 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -6,6 +6,8 @@ set(ZLIB_ROOT "${ZLIB_ROOT}" CACHE STRING "ZLib root location" FORCE) mark_as_advanced(ZLIB_ROOT) # Png depends on zlib add_subdirectory(zlib ${TORQUE_LIB_TARG_DIRECTORY}/zlib EXCLUDE_FROM_ALL) +set(ZLIB_FOUND 1) +set(ZLIB_LIBRARIES zlib) if(APPLE) enable_language(OBJC) @@ -116,8 +118,7 @@ add_subdirectory(convexMath ${TORQUE_LIB_TARG_DIRECTORY}/convexMath EXCLUDE_FROM # Assimp advanced_option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) -set(ASSIMP_BUILD_NO_OWN_ZLIB ON CACHE BOOL "" FORCE) -mark_as_advanced(ASSIMP_BUILD_NO_OWN_ZLIB) +advanced_option(ASSIMP_BUILD_NO_OWN_ZLIB "" ON) advanced_option(BUILD_SHARED_LIBS "Build package with shared libraries." OFF ) advanced_option(ASSIMP_BUILD_FRAMEWORK "Build package as Mac OS X Framework bundle." OFF ) advanced_option(ASSIMP_DOUBLE_PRECISION "Set to ON to enable double precision processing" OFF ) diff --git a/Engine/lib/assimp/Build.md b/Engine/lib/assimp/Build.md index d8e6c50f6..0adb437b6 100644 --- a/Engine/lib/assimp/Build.md +++ b/Engine/lib/assimp/Build.md @@ -1,36 +1,10 @@ # Build / Install Instructions -## Install on all platforms using vcpkg -You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: -```bash - git clone https://github.com/Microsoft/vcpkg.git - cd vcpkg - ./bootstrap-vcpkg.sh - ./vcpkg integrate install - vcpkg install assimp -``` -The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. - -## Install on Ubuntu -You can install the Asset-Importer-Lib via apt: -``` -sudo apt-get update -sudo apt-get install libassimp-dev -``` - -## Install pyassimp -You need to have pip installed: -``` -pip install pyassimp -``` - ## Manual build instructions - -### Install CMake -Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via -```bash -sudo apt-get install cmake -``` +### Install prerequisites +You need to install +* cmake +* Your compiler ### Get the source Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via: @@ -38,35 +12,32 @@ Make sure you have a working git-installation. Open a command prompt and clone t git clone https://github.com/assimp/assimp.git ``` ### Build from source: +* For *assimp.lib* without any tools: ```bash cd assimp -cmake CMakeLists.txt +cmake CMakeLists.txt cmake --build . ``` -### Build instructions for Windows with Visual-Studio +* For assimp with the common tools like *assimp-cmd* +```bash +cd assimp +cmake CMakeLists.txt -DASSIMP_BUILD_ASSIMP_TOOLS=ON +cmake --build . +``` +Note that by default this builds a shared library into the `bin` directory. If you want to build it as a static library see the build options at the bottom of this file. -First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/ +### Build instructions for Windows with Visual-Studio +First, you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/ To generate the build environment for your IDE open a command prompt, navigate to your repo and type: ```bash cmake CMakeLists.txt ``` -This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it. +This will generate the project files for the visual studio. All dependencies used to build Asset-Importer-Lib shall be part of the repo. If you want to use you own zlib installation this is possible as well. Check the options for it. ### Build instructions for Windows with UWP See -### Build instructions for Linux / Unix -Open a terminal and got to your repository. You can generate the makefiles and build the library via: - -```bash -cmake CMakeLists.txt -make -j4 -``` -The option -j descripes the number of parallel processes for the build. In this case make will try to use 4 cores for the build. - -If you want to use a IDE for linux you can try QTCreator for instance. - ### Build instructions for MinGW Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag required to compile some of assimp's files, especially for debug builds. @@ -93,9 +64,9 @@ The cmake-build-environment provides options to configure the build. The followi - **ASSIMP_ANDROID_JNIIOSYSTEM (default OFF)**: Android JNI IOSystem support is active. - **ASSIMP_NO_EXPORT (default OFF)**: Disable Assimp's export functionality. - **ASSIMP_BUILD_ZLIB (default OFF)**: Build our own zlib. -- **ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT (default ON)**: Build Assimp with all exporter senabled. -- **ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT (default ON)**: Build Assimp with all importer senabled. -- **ASSIMP_BUILD_ASSIMP_TOOLS (default ON)**: If the supplementary tools for Assimp are built in addition to the library. +- **ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT (default ON)**: Build Assimp with all exporters enabled. +- **ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT (default ON)**: Build Assimp with all importers enabled. +- **ASSIMP_BUILD_ASSIMP_TOOLS (default OFF)**: If the supplementary tools for Assimp are built in addition to the library. - **ASSIMP_BUILD_SAMPLES (default OFF)**: If the official samples are built as well (needs Glut). - **ASSIMP_BUILD_TESTS (default ON)**: If the test suite for Assimp is built in addition to the library. - **ASSIMP_COVERALLS (default OFF)**: Enable this to measure test coverage. @@ -110,3 +81,31 @@ The cmake-build-environment provides options to configure the build. The followi - **USE_STATIC_CRT (default OFF)**: Link against the static MSVC runtime libraries. - **ASSIMP_BUILD_DRACO (default OFF)**: Build Draco libraries. Primarily for glTF. - **ASSIMP_BUILD_ASSIMP_VIEW (default ON, if DirectX found, OFF otherwise)**: Build Assimp view tool (requires DirectX). + +### Install prebuild binaries +## Install on all platforms using vcpkg +You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: +```bash + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install assimp +``` +The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +### Install on Ubuntu +You can install the Asset-Importer-Lib via apt: +``` +sudo apt-get update +sudo apt-get install libassimp-dev +``` + +### Install pyassimp +You need to have pip installed: +``` +pip install pyassimp +``` + +### Get the SDK from itchi.io +Just check [itchi.io](https://kimkulling.itch.io/the-asset-importer-lib) diff --git a/Engine/lib/assimp/CMakeLists.txt b/Engine/lib/assimp/CMakeLists.txt index 458a32834..8eb9c54eb 100644 --- a/Engine/lib/assimp/CMakeLists.txt +++ b/Engine/lib/assimp/CMakeLists.txt @@ -1,6 +1,6 @@ # Open Asset Import Library (assimp) # ---------------------------------------------------------------------- -# Copyright (c) 2006-2022, assimp team +# Copyright (c) 2006-2024, assimp team # # All rights reserved. # @@ -38,25 +38,50 @@ SET(CMAKE_POLICY_DEFAULT_CMP0012 NEW) SET(CMAKE_POLICY_DEFAULT_CMP0074 NEW) SET(CMAKE_POLICY_DEFAULT_CMP0092 NEW) -CMAKE_MINIMUM_REQUIRED( VERSION 3.10 ) +CMAKE_MINIMUM_REQUIRED( VERSION 3.22 ) + +# Experimental USD importer: disabled, need to opt-in +# Note: assimp github PR automatic checks will fail the PR due to compiler warnings in +# the external, 3rd party tinyusdz code which isn't technically part of the PR since it's +# auto-cloned during build; so MUST disable the feature or the PR will be rejected +option(ASSIMP_BUILD_USD_IMPORTER "Enable USD file import" off) +option(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" off) +option(ASSIMP_BUILD_USE_CCACHE "Use ccache to speed up compilation." on) + +if(ASSIMP_BUILD_USE_CCACHE) + find_program(CCACHE_PATH ccache) + if (CCACHE_PATH) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PATH}) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PATH}) + endif() +endif() + +# User may override these in their CMake script to provide M3D import/export support +# (M3D importer/exporter was disabled for assimp release 5.1 or later) +option(ASSIMP_BUILD_M3D_IMPORTER "Enable M3D file import" off) +option(ASSIMP_BUILD_M3D_EXPORTER "Enable M3D file export" off) + +# Internal/private M3D logic +if (NOT ASSIMP_BUILD_M3D_IMPORTER) + ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER) +endif () # if (not ASSIMP_BUILD_M3D_IMPORTER) +if (NOT ASSIMP_BUILD_M3D_EXPORTER) + ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER) +endif () # if (not ASSIMP_BUILD_M3D_EXPORTER) -# Disabled importers: m3d for 5.1 -ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER) -ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER) # Toggles the use of the hunter package manager -option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) +option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" ON) IF(ASSIMP_HUNTER_ENABLED) include("cmake-modules/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.24.0.tar.gz" - SHA1 "a3d7f4372b1dcd52faa6ff4a3bd5358e1d0e5efd" + URL "https://github.com/cpp-pm/hunter/archive/v0.25.8.tar.gz" + SHA1 "26c79d587883ec910bce168e25f6ac4595f97033" ) - add_definitions(-DASSIMP_USE_HUNTER) ENDIF() -PROJECT(Assimp VERSION 5.2.4) +PROJECT(Assimp VERSION 5.4.3) # All supported options ############################################### @@ -84,10 +109,6 @@ OPTION( ASSIMP_NO_EXPORT "Disable Assimp's export functionality." OFF ) -OPTION( ASSIMP_BUILD_ZLIB - "Build your own zlib" - OFF -) OPTION( ASSIMP_BUILD_ASSIMP_TOOLS "If the supplementary tools for Assimp are built in addition to the library." OFF @@ -106,7 +127,7 @@ OPTION ( ASSIMP_COVERALLS ) OPTION( ASSIMP_INSTALL "Disable this if you want to use assimp as a submodule." - ON + OFF ) OPTION ( ASSIMP_WARNINGS_AS_ERRORS "Treat all warnings as errors." @@ -135,24 +156,36 @@ OPTION ( ASSIMP_IGNORE_GIT_HASH ) IF (WIN32) - # Use subset of Windows.h + OPTION( ASSIMP_BUILD_ZLIB + "Build your zlib" + ON + ) +ELSE() + OPTION( ASSIMP_BUILD_ZLIB + "Build your zlib" + OFF + ) +ENDIF() + +IF (WIN32) + # Use a subset of Windows.h ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN ) IF(MSVC) OPTION( ASSIMP_INSTALL_PDB - "Install MSVC debug files." + "Create MSVC debug symbol files and add to Install target." ON ) IF(NOT (MSVC_VERSION LESS 1900)) - # Multibyte character set is deprecated since at least MSVC2015 (possibly earlier) + # Multibyte character set has been deprecated since at least MSVC2015 (possibly earlier) ADD_DEFINITIONS( -DUNICODE -D_UNICODE ) ENDIF() - # Link statically against c/c++ lib to avoid missing redistriburable such as + # Link statically against c/c++ lib to avoid missing redistributable such as # "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users # a choice to opt for the shared runtime if they want. option(USE_STATIC_CRT "Link against the static runtime libraries." OFF) - # The CMAKE_CXX_FLAGS vars can be overriden by some Visual Studio generators, so we use an alternative + # The CMAKE_CXX_FLAGS vars can be overridden by some Visual Studio generators, so we use an alternative # global method here: if (${USE_STATIC_CRT}) add_compile_options( @@ -193,12 +226,9 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER SET (ASSIMP_SOVERSION 5) SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" ) -if(NOT ASSIMP_HUNTER_ENABLED) - # Enable C++17 support globally - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_C_STANDARD 99) -endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD 99) IF(NOT ASSIMP_IGNORE_GIT_HASH) # Get the current working branch @@ -245,49 +275,60 @@ SET(ASSIMP_LIBRARY_SUFFIX "" CACHE STRING "Suffix to append to library names") IF( UNIX ) # Use GNUInstallDirs for Unix predefined directories INCLUDE(GNUInstallDirs) - # Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux - IF( ${OPERATING_SYSTEM} MATCHES "Android") - ELSE() - IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux + # Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit Linux + IF(NOT ${OPERATING_SYSTEM} MATCHES "Android") + IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit Linux ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 ) ENDIF() ENDIF() ENDIF() # Grouped compiler settings ######################################## -IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW) +IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU) IF(NOT ASSIMP_HUNTER_ENABLED) - SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) ENDIF() + + IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") + MESSAGE(STATUS "GCC13 detected disabling \"-Wdangling-reference\" in Cpp files as it appears to be a false positive") + ADD_COMPILE_OPTIONS("$<$:-Wno-dangling-reference>") + ENDIF() # hide all not-exported symbols IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "mips64" ) - SET(CMAKE_CXX_FLAGS "-mxgot -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") - SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") - SET(LIBSTDC++_LIBRARIES -lstdc++) + SET(CMAKE_CXX_FLAGS "-mxgot -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") + SET(LIBSTDC++_LIBRARIES -lstdc++) ELSE() - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") - SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") - SET(LIBSTDC++_LIBRARIES -lstdc++) + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") + SET(LIBSTDC++_LIBRARIES -lstdc++) ENDIF() ELSEIF(MSVC) # enable multi-core compilation with MSVC IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl - ADD_COMPILE_OPTIONS(/bigobj /W4 /WX ) + ADD_COMPILE_OPTIONS(/bigobj) ELSE() # msvc - ADD_COMPILE_OPTIONS(/MP /bigobj /W4 /WX) + ADD_COMPILE_OPTIONS(/MP /bigobj) ENDIF() + # disable "elements of array '' will be default initialized" warning on MSVC2013 IF(MSVC12) - ADD_COMPILE_OPTIONS(/wd4351) + ADD_COMPILE_OPTIONS(/wd4351) ENDIF() - ADD_COMPILE_OPTIONS(/wd4244) #supress warning for double to float conversion if Double precission is activated + # supress warning for double to float conversion if Double precision is activated + ADD_COMPILE_OPTIONS(/wd4244) SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od") - SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") - SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF") + # Allow user to disable PDBs + if(ASSIMP_INSTALL_PDB) + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF") + elseif((GENERATOR_IS_MULTI_CONFIG) OR (CMAKE_BUILD_TYPE MATCHES Release)) + message("-- MSVC PDB generation disabled. Release binary will not be debuggable.") + endif() + # Source code is encoded in UTF-8 + ADD_COMPILE_OPTIONS(/source-charset:utf-8) ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) IF(NOT ASSIMP_HUNTER_ENABLED) - SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) ENDIF() SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" ) @@ -303,26 +344,26 @@ ELSEIF( MINGW ) SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") ENDIF() IF (CMAKE_BUILD_TYPE STREQUAL "Debug") - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -g ${CMAKE_CXX_FLAGS}") + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wno-dangling-reference -Wall -Wno-long-long -Wa,-mbig-obj -g ${CMAKE_CXX_FLAGS}") ELSE() - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}") + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wno-dangling-reference -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}") ENDIF() SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") ENDIF() IF ( IOS AND NOT ASSIMP_HUNTER_ENABLED) IF (CMAKE_BUILD_TYPE STREQUAL "Debug") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og") ELSE() - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3") - # Experimental for pdb generation ENDIF() ENDIF() IF (ASSIMP_COVERALLS) MESSAGE(STATUS "Coveralls enabled") + INCLUDE(Coveralls) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") @@ -330,14 +371,16 @@ ENDIF() IF (ASSIMP_ASAN) MESSAGE(STATUS "AddressSanitizer enabled") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") ENDIF() IF (ASSIMP_UBSAN) MESSAGE(STATUS "Undefined Behavior sanitizer enabled") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all") ENDIF() INCLUDE (FindPkgMacros) @@ -388,13 +431,6 @@ IF (NOT TARGET uninstall AND ASSIMP_INSTALL) ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") ENDIF() -# cmake configuration files -if(${BUILD_SHARED_LIBS}) - set(BUILD_LIB_TYPE SHARED) -else() - set(BUILD_LIB_TYPE STATIC) -endif() - IF( UNIX ) # Use GNUInstallDirs for Unix predefined directories INCLUDE(GNUInstallDirs) @@ -449,18 +485,20 @@ configure_package_config_file( INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}" ) -install( - FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}" - DESTINATION "${CONFIG_INSTALL_DIR}" - COMPONENT ${LIBASSIMP-DEV_COMPONENT} -) +if(ASSIMP_INSTALL) + install( + FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}" + DESTINATION "${CONFIG_INSTALL_DIR}" + COMPONENT ${LIBASSIMP-DEV_COMPONENT} + ) -install( - EXPORT "${TARGETS_EXPORT_NAME}" - NAMESPACE "${NAMESPACE}" - DESTINATION "${CONFIG_INSTALL_DIR}" - COMPONENT ${LIBASSIMP-DEV_COMPONENT} -) + install( + EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${NAMESPACE}" + DESTINATION "${CONFIG_INSTALL_DIR}" + COMPONENT ${LIBASSIMP-DEV_COMPONENT} + ) +endif() IF( ASSIMP_BUILD_DOCS ) ADD_SUBDIRECTORY(doc) @@ -473,12 +511,12 @@ IF(ASSIMP_HUNTER_ENABLED) find_package(ZLIB CONFIG REQUIRED) add_definitions(-DASSIMP_BUILD_NO_OWN_ZLIB) - set(ZLIB_FOUND TRUE) + set( TRUE) set(ZLIB_LIBRARIES ZLIB::zlib) set(ASSIMP_BUILD_MINIZIP TRUE) ELSE() # If the zlib is already found outside, add an export in case assimpTargets can't find it. - IF( ZLIB_FOUND ) + IF( AND ASSIMP_INSTALL) INSTALL( TARGETS zlib zlibstatic EXPORT "${TARGETS_EXPORT_NAME}") ENDIF() @@ -487,7 +525,11 @@ ELSE() FIND_PACKAGE(ZLIB) ENDIF() - IF( NOT ZLIB_FOUND ) + IF ( NOT AND NOT ASSIMP_BUILD_ZLIB ) + message( FATAL_ERROR + "Build configured with -DASSIMP_BUILD_ZLIB=OFF but unable to find zlib" + ) + ELSEIF( NOT ) MESSAGE(STATUS "compiling zlib from sources") INCLUDE(CheckIncludeFile) INCLUDE(CheckTypeSize) @@ -503,7 +545,7 @@ ELSE() # compile from sources ADD_SUBDIRECTORY(contrib/zlib) - SET(ZLIB_FOUND 1) + SET( 1) SET(ZLIB_LIBRARIES zlibstatic) SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib) # need to ensure we don't link with system zlib or minizip as well. @@ -556,11 +598,15 @@ SET ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER OFF CACHE BOOL ) IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) - IF ( MSVC ) - SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/includes") + SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/includes") + IF (WIN32) # pick the correct prebuilt library - IF(MSVC15) + IF(MSVC143) + SET(C4D_LIB_POSTFIX "_2022") + ELSEIF(MSV142) + SET(C4D_LIB_POSTFIX "_2019") + ELSEIF(MSVC15) SET(C4D_LIB_POSTFIX "_2017") ELSEIF(MSVC14) SET(C4D_LIB_POSTFIX "_2015") @@ -572,7 +618,7 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) SET(C4D_LIB_POSTFIX "_2010") ELSE() MESSAGE( FATAL_ERROR - "C4D is currently only supported with MSVC 10, 11, 12, 14" + "C4D for Windows is currently only supported with MSVC 10, 11, 12, 14, 14.2, 14.3" ) ENDIF() @@ -590,15 +636,30 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) # winsock and winmm are necessary (and undocumented) dependencies of Cineware SDK because # it can be used to communicate with a running Cinema 4D instance SET(C4D_EXTRA_LIBRARIES WSock32.lib Winmm.lib) + ELSEIF (APPLE) + SET(C4D_LIB_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Cineware/libraries/osx") + + SET(C4D_DEBUG_LIBRARIES + "${C4D_LIB_BASE_PATH}/debug/libcinewarelib.a" + "${C4D_LIB_BASE_PATH}/debug/libjpeglib.a" + ) + SET(C4D_RELEASE_LIBRARIES + "${C4D_LIB_BASE_PATH}/release/libcinewarelib.a" + "${C4D_LIB_BASE_PATH}/release/libjpeglib.a" + ) ELSE () MESSAGE( FATAL_ERROR - "C4D is currently only available on Windows with Cineware SDK installed in contrib/Cineware" + "C4D is currently only available on Windows and macOS with Cineware SDK installed in contrib/Cineware" ) ENDIF () ELSE () ADD_DEFINITIONS( -DASSIMP_BUILD_NO_C4D_IMPORTER ) ENDIF () +if(ASSIMP_BUILD_DRACO_STATIC) + set(ASSIMP_BUILD_DRACO ON) +endif() + # Draco requires cmake 3.12 IF (DEFINED CMAKE_VERSION AND "${CMAKE_VERSION}" VERSION_LESS "3.12") message(NOTICE "draco requires cmake 3.12 or newer, cmake is ${CMAKE_VERSION} . Draco is disabled") @@ -608,7 +669,7 @@ ELSE() IF ( ASSIMP_BUILD_DRACO ) # Primarily for glTF v2 # Enable Draco glTF feature set - set(DRACO_GLTF ON CACHE BOOL "" FORCE) + set(DRACO_GLTF_BITSTREAM ON CACHE BOOL "" FORCE) # Disable unnecessary or omitted components set(DRACO_JS_GLUE OFF CACHE BOOL "" FORCE) set(DRACO_WASM OFF CACHE BOOL "" FORCE) @@ -634,22 +695,29 @@ ELSE() "-Wno-sign-compare" "-Wno-unused-local-typedefs" ) - # Draco 1.4.1 does not explicitly export any symbols under GCC/clang - list(APPEND DRACO_CXX_FLAGS - "-fvisibility=default" - ) + + if(NOT ASSIMP_BUILD_DRACO_STATIC) + # Draco 1.4.1 does not explicitly export any symbols under GCC/clang + list(APPEND DRACO_CXX_FLAGS + "-fvisibility=default" + ) + endif() ENDIF() # Don't build or install all of Draco by default ADD_SUBDIRECTORY( "contrib/draco" EXCLUDE_FROM_ALL ) + if(ASSIMP_BUILD_DRACO_STATIC) + set_property(DIRECTORY "contrib/draco" PROPERTY BUILD_SHARED_LIBS OFF) + endif() + if(MSVC OR WIN32) set(draco_LIBRARIES "draco") else() - if(BUILD_SHARED_LIBS) - set(draco_LIBRARIES "draco_shared") - else() + if(ASSIMP_BUILD_DRACO_STATIC) set(draco_LIBRARIES "draco_static") + else() + set(draco_LIBRARIES "draco_shared") endif() endif() @@ -657,13 +725,13 @@ ELSE() set_target_properties(draco_encoder draco_decoder PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE - ) + ) # Do build the draco shared library set_target_properties(${draco_LIBRARIES} PROPERTIES EXCLUDE_FROM_ALL FALSE EXCLUDE_FROM_DEFAULT_BUILD FALSE - ) + ) TARGET_USE_COMMON_OUTPUT_DIRECTORY(${draco_LIBRARIES}) TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_encoder) @@ -672,16 +740,17 @@ ELSE() set(draco_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/contrib/draco/src") # This is probably wrong - INSTALL( TARGETS ${draco_LIBRARIES} - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR} - FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - COMPONENT ${LIBASSIMP_COMPONENT} - INCLUDES DESTINATION include - ) - + if (ASSIMP_INSTALL) + INSTALL( TARGETS ${draco_LIBRARIES} + EXPORT "${TARGETS_EXPORT_NAME}" + LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR} + FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + COMPONENT ${LIBASSIMP_COMPONENT} + INCLUDES DESTINATION include + ) + endif() ENDIF() ENDIF() ENDIF() @@ -725,8 +794,8 @@ IF ( ASSIMP_INSTALL ) ENDIF() CONFIGURE_FILE( - ${CMAKE_CURRENT_LIST_DIR}/revision.h.in - ${CMAKE_CURRENT_BINARY_DIR}/revision.h + ${CMAKE_CURRENT_LIST_DIR}/include/assimp/revision.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/assimp/revision.h ) CONFIGURE_FILE( @@ -767,7 +836,7 @@ IF ( ASSIMP_INSTALL ) SET(CPACK_DEBIAN_PACKAGE_SECTION "libs" ) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_COMPONENTS_ALL}") SET(CPACK_DEBIAN_PACKAGE_SUGGESTS) - SET(cPACK_DEBIAN_PACKAGE_NAME "assimp") + SET(CPACK_DEBIAN_PACKAGE_NAME "assimp") SET(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES contrib/gtest contrib/zlib workspaces test doc obj samples packaging) SET(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force) SET(CPACK_DEBIAN_CHANGELOG) @@ -804,6 +873,10 @@ if(WIN32) SET(ASSIMP_MSVC_VERSION "vc140") ELSEIF(MSVC15) SET(ASSIMP_MSVC_VERSION "vc141") + ELSEIF(MSV142) + SET(ASSIMP_MSVC_VERSION "vc142") + ELSEIF(MSVC143) + SET(ASSIMP_MSVC_VERSION "vc143") ENDIF() ENDIF() diff --git a/Engine/lib/assimp/CODE_OF_CONDUCT.md b/Engine/lib/assimp/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..18c914718 --- /dev/null +++ b/Engine/lib/assimp/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Engine/lib/assimp/Dockerfile b/Engine/lib/assimp/Dockerfile index b65d131a4..716e8b5d8 100644 --- a/Engine/lib/assimp/Dockerfile +++ b/Engine/lib/assimp/Dockerfile @@ -1,16 +1,12 @@ -FROM ubuntu:14.04 +FROM ubuntu:22.04 -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install --no-install-recommends -y ninja-build \ git cmake build-essential software-properties-common -RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update && apt-get install -y gcc-4.9 g++-4.9 && \ - cd /usr/bin && \ - rm gcc g++ cpp && \ - ln -s gcc-4.9 gcc && \ - ln -s g++-4.9 g++ && \ - ln -s cpp-4.9 cpp +RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update WORKDIR /opt +RUN apt install zlib1g-dev # Build Assimp RUN git clone https://github.com/assimp/assimp.git /opt/assimp @@ -19,7 +15,8 @@ WORKDIR /opt/assimp RUN git checkout master \ && mkdir build && cd build && \ - cmake \ + cmake -G 'Ninja' \ -DCMAKE_BUILD_TYPE=Release \ + -DASSIMP_BUILD_ASSIMP_TOOLS=ON \ .. && \ - make && make install + ninja -j4 && ninja install diff --git a/Engine/lib/assimp/Readme.md b/Engine/lib/assimp/Readme.md index 0a04da999..8dc479c9f 100644 --- a/Engine/lib/assimp/Readme.md +++ b/Engine/lib/assimp/Readme.md @@ -1,42 +1,45 @@ Open Asset Import Library (assimp) ================================== -A library to import and export various 3d-model-formats including scene-post-processing to generate missing render data. -### Current project status ### -[![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp) -![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg) - - Coverity Scan Build Status - -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade) -[![Coverage Status](https://coveralls.io/repos/github/assimp/assimp/badge.svg?branch=master)](https://coveralls.io/github/assimp/assimp?branch=master) -[![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +Open Asset Import Library is a library that loads various 3D file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export. + +### Current project status ### +![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=assimp_assimp&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=assimp_assimp) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open") -[![Total alerts](https://img.shields.io/lgtm/alerts/g/assimp/assimp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/assimp/assimp/alerts/) +[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20assimp%20Guru-006BFF)](https://gurubase.io/g/assimp) +[![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp)
-APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS. -Additionally, assimp features various __mesh post processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more. +APIs are provided for C and C++. Various bindings exist to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS. +Additionally, assimp features various __mesh post-processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more. -### Latest Doc's ### -Please check the latest documents at [Asset-Importer-Lib-Doc](https://assimp-docs.readthedocs.io/en/latest/). +## Project activity ## +![Alt](https://repobeats.axiom.co/api/embed/997f84e5f9fcf772da1e687f3a4f3a8afdbf4cf0.svg "Repobeats analytics image") -### Get involved ### -This is the development repo containing the latest features and bugfixes. For productive use though, we recommend one of the stable releases available from [Github Assimp Releases](https://github.com/assimp/assimp/releases). -
-You find a bug in the docs? Use [Doc-Repo](https://github.com/assimp/assimp-docs). -
-Please check our Wiki as well: https://github.com/assimp/assimp/wiki +### Documentation ### +Read [our latest documentation](https://assimp-docs.readthedocs.io/en/latest/). -If you want to check our Model-Database, use the following repo: https://github.com/assimp/assimp-mdb +### Pre-built binaries ### +Download binaries from [our Itchi Projectspace](https://kimkulling.itch.io/the-asset-importer-lib). + +### Test data ### +Clone [our model database](https://github.com/assimp/assimp-mdb). + +### Communities ### +- Ask questions at [the Assimp Discussion Board](https://github.com/assimp/assimp/discussions). +- Find us on [https://discord.gg/s9KJfaem](https://discord.gg/kKazXMXDy2) +- Ask [the Assimp community on Reddit](https://www.reddit.com/r/Assimp/). +- Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). +- Nothing has worked? File a question or an issue report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues) #### Supported file formats #### -You can find the complete list of supported file-formats [here](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md) +See [the complete list of supported formats](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md). ### Building ### -Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. We are available in vcpkg, and our build system is CMake; if you used CMake before there is a good chance you know what to do. +Start by reading [our build instructions](https://github.com/assimp/assimp/blob/master/Build.md). We are available in vcpkg, and our build system is CMake; if you used CMake before there is a good chance you know what to do. ### Ports ### * [Android](port/AndroidJNI/README.md) @@ -47,46 +50,40 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. * [Javascript/Node.js Interface](https://github.com/kovacsv/assimpjs) * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/) * [Unreal Engine Plugin](https://github.com/irajsb/UE4_Assimp/) -* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status)) +* [JVM](https://github.com/kotlin-graphics/assimp) Full JVM port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status)) * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port. * [Rust](https://github.com/jkvargas/russimp) ### Other tools ### [open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities. +[Assimp-Viewer](https://github.com/assimp/assimp_view) is an experimental implementation for an Asset-Viewer based on ImGUI and Assimp (experimental). #### Repository structure #### -Open Asset Import Library is implemented in C++. The directory structure looks like: +Open Asset Import Library is implemented in C++. The directory structure looks like this: /code Source code /contrib Third-party libraries - /doc Documentation (doxysource and pre-compiled docs) - /fuzz Contains the test-code for the Google-Fuzzer project + /doc Documentation (Doxygen source and pre-compiled docs) + /fuzz Contains the test code for the Google Fuzzer project /include Public header C and C++ header files - /scripts Scripts used to generate the loading code for some formats + /scripts Scripts are used to generate the loading code for some formats /port Ports to other languages and scripts to maintain those. /test Unit- and regression tests, test suite of models /tools Tools (old assimp viewer, command line `assimp`) - /samples A small number of samples to illustrate possible - use cases for Assimp + /samples A small number of samples to illustrate possible use cases for Assimp The source code is organized in the following way: code/Common The base implementation for importers and the infrastructure + code/CApi Special implementations which are only used for the C-API + code/Geometry A collection of geometry tools + code/Material The material system + code/PBR An exporter for physical-based models code/PostProcessing The post-processing steps - code/AssetLib/ Implementation for import and export for the format - -### Where to get help ### -For more information, visit [our website](http://assimp.org/). Or check out the `./doc`- folder, which contains the official documentation in HTML format. -(CHMs for Windows are included in some release packages and should be located right here in the root folder). - -If the docs don't solve your problem, ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). If you think you found a bug, please open an issue on Github. - -Open Asset Import Library is a library to load various 3d file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export. - -And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+ code/AssetLib/ Implementation for import and export of the format ### Contributing ### -Contributions to assimp are highly appreciated. The easiest way to get involved is to submit +I would greatly appreciate contributing to assimp. The easiest way to get involved is to submit a pull request with your changes against the main repository's `master` branch. ## Contributors @@ -108,7 +105,7 @@ Become a financial contributor and help us sustain our community. [[Contribute]( #### Organizations -Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/assimp/contribute)] +You can support the project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/assimp/contribute)] @@ -118,6 +115,3 @@ Our license is based on the modified, __3-clause BSD__-License. An _informal_ summary is: do whatever you want, but include Assimp's license text with your product - and don't sue us if our code doesn't work. Note that, unlike LGPLed code, you may link statically to Assimp. For the legal details, see the `LICENSE` file. - -### Why this name ### -Sorry, we're germans :-), no english native speakers ... diff --git a/Engine/lib/assimp/cmake-modules/HunterGate.cmake b/Engine/lib/assimp/cmake-modules/HunterGate.cmake index 6d9cc2401..fe02e3614 100644 --- a/Engine/lib/assimp/cmake-modules/HunterGate.cmake +++ b/Engine/lib/assimp/cmake-modules/HunterGate.cmake @@ -22,38 +22,9 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# This is a gate file to Hunter package manager. -# Include this file using `include` command and add package you need, example: -# -# cmake_minimum_required(VERSION 3.2) -# -# include("cmake/HunterGate.cmake") -# HunterGate( -# URL "https://github.com/path/to/hunter/archive.tar.gz" -# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" -# ) -# -# project(MyProject) -# -# hunter_add_package(Foo) -# hunter_add_package(Boo COMPONENTS Bar Baz) -# -# Projects: -# * https://github.com/hunter-packages/gate/ -# * https://github.com/ruslo/hunter option(HUNTER_ENABLED "Enable Hunter package manager support" ON) -if(HUNTER_ENABLED) - if(CMAKE_VERSION VERSION_LESS "3.2") - message( - FATAL_ERROR - "At least CMake version 3.2 required for Hunter dependency management." - " Update CMake or set HUNTER_ENABLED to OFF." - ) - endif() -endif() - include(CMakeParseArguments) # cmake_parse_arguments option(HUNTER_STATUS_PRINT "Print working status" ON) diff --git a/Engine/lib/assimp/code/.editorconfig b/Engine/lib/assimp/code/.editorconfig deleted file mode 100644 index 4a194a317..000000000 --- a/Engine/lib/assimp/code/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -# See for details - -[*.{h,hpp,c,cpp}] -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_size = 4 -indent_style = space diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSConverter.cpp b/Engine/lib/assimp/code/AssetLib/3DS/3DSConverter.cpp index b4f625b76..50600ba10 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSConverter.cpp +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSConverter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,9 +52,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const unsigned int NotSet = 0xcdcdcdcd; +static constexpr unsigned int NotSet = 0xcdcdcdcd; // ------------------------------------------------------------------------------------------------ // Setup final material indices, generae a default material if necessary @@ -68,7 +68,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() { unsigned int idx(NotSet); for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) { std::string s = mScene->mMaterials[i].mName; - for (char & it : s) { + for (char &it : s) { it = static_cast(::tolower(static_cast(it))); } @@ -262,7 +262,7 @@ void Discreet3DSImporter::ConvertMaterial(D3DS::Material &oldMat, unsigned int iWire = 1; mat.AddProperty((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); } - [[fallthrough]]; + [[fallthrough]]; case D3DS::Discreet3DS::Gouraud: eShading = aiShadingMode_Gouraud; @@ -593,7 +593,7 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, // Cameras or lights define their transformation in their parent node and in the // corresponding light or camera chunks. However, we read and process the latter - // to to be able to return valid cameras/lights even if no scenegraph is given. + // to be able to return valid cameras/lights even if no scenegraph is given. for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) { if (pcSOut->mCameras[n]->mName == pcOut->mName) { pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f); @@ -643,11 +643,17 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, } // Allocate storage for children - pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size(); + const unsigned int size = static_cast(pcIn->mChildren.size()); + + pcOut->mNumChildren = size; + if (size == 0) { + return; + } + pcOut->mChildren = new aiNode *[pcIn->mChildren.size()]; // Recursively process all children - const unsigned int size = static_cast(pcIn->mChildren.size()); + for (unsigned int i = 0; i < size; ++i) { pcOut->mChildren[i] = new aiNode(); pcOut->mChildren[i]->mParent = pcOut; @@ -709,7 +715,7 @@ void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) { pcNode->mNumMeshes = 1; // Build a name for the node - pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u", i); + pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "3DSMesh_%u", i); } // Build dummy nodes for all cameras @@ -805,4 +811,6 @@ void Discreet3DSImporter::ConvertScene(aiScene *pcOut) { } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.cpp b/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.cpp index 1b335a272..5341a69f1 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.h b/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.h index 82ec3512f..9e3e42911 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.h +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSExporter.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,8 +56,7 @@ struct aiNode; struct aiMaterial; struct aiMesh; -namespace Assimp -{ +namespace Assimp { // ------------------------------------------------------------------------------------------------ /** @@ -88,7 +86,7 @@ private: std::map trafos; - typedef std::multimap MeshesByNodeMap; + using MeshesByNodeMap = std::multimap; MeshesByNodeMap meshes; }; diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSHelper.h b/Engine/lib/assimp/code/AssetLib/3DS/3DSHelper.h index dc1098035..271a2cc7b 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSHelper.h +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSHelper.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -322,7 +322,6 @@ struct Texture { //! Default constructor Texture() AI_NO_EXCEPT : mTextureBlend(0.0f), - mMapName(), mOffsetU(0.0), mOffsetV(0.0), mScaleU(1.0), @@ -334,51 +333,11 @@ struct Texture { mTextureBlend = get_qnan(); } - Texture(const Texture &other) : - mTextureBlend(other.mTextureBlend), - mMapName(other.mMapName), - mOffsetU(other.mOffsetU), - mOffsetV(other.mOffsetV), - mScaleU(other.mScaleU), - mScaleV(other.mScaleV), - mRotation(other.mRotation), - mMapMode(other.mMapMode), - bPrivate(other.bPrivate), - iUVSrc(other.iUVSrc) { - // empty - } + Texture(const Texture &other) = default; - Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(other.mTextureBlend), - mMapName(std::move(other.mMapName)), - mOffsetU(other.mOffsetU), - mOffsetV(other.mOffsetV), - mScaleU(other.mScaleU), - mScaleV(other.mScaleV), - mRotation(other.mRotation), - mMapMode(other.mMapMode), - bPrivate(other.bPrivate), - iUVSrc(other.iUVSrc) { - // empty - } + Texture(Texture &&other) AI_NO_EXCEPT = default; - Texture &operator=(Texture &&other) AI_NO_EXCEPT { - if (this == &other) { - return *this; - } - - mTextureBlend = other.mTextureBlend; - mMapName = std::move(other.mMapName); - mOffsetU = other.mOffsetU; - mOffsetV = other.mOffsetV; - mScaleU = other.mScaleU; - mScaleV = other.mScaleV; - mRotation = other.mRotation; - mMapMode = other.mMapMode; - bPrivate = other.bPrivate; - iUVSrc = other.iUVSrc; - - return *this; - } + Texture &operator=(Texture &&other) AI_NO_EXCEPT = default; //! Specifies the blend factor for the texture ai_real mTextureBlend; @@ -406,14 +365,13 @@ struct Texture { #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER - // --------------------------------------------------------------------------- /** Helper structure representing a 3ds material */ struct Material { //! Default constructor has been deleted Material() : mName(), - mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mDiffuse(0.6f, 0.6f, 0.6f), mSpecularExponent(ai_real(0.0)), mShininessStrength(ai_real(1.0)), mShading(Discreet3DS::Gouraud), @@ -426,7 +384,7 @@ struct Material { //! Constructor with explicit name explicit Material(const std::string &name) : mName(name), - mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mDiffuse(0.6f, 0.6f, 0.6f), mSpecularExponent(ai_real(0.0)), mShininessStrength(ai_real(1.0)), mShading(Discreet3DS::Gouraud), @@ -436,83 +394,9 @@ struct Material { // empty } - Material(const Material &other) : - mName(other.mName), - mDiffuse(other.mDiffuse), - mSpecularExponent(other.mSpecularExponent), - mShininessStrength(other.mShininessStrength), - mSpecular(other.mSpecular), - mAmbient(other.mAmbient), - mShading(other.mShading), - mTransparency(other.mTransparency), - sTexDiffuse(other.sTexDiffuse), - sTexOpacity(other.sTexOpacity), - sTexSpecular(other.sTexSpecular), - sTexReflective(other.sTexReflective), - sTexBump(other.sTexBump), - sTexEmissive(other.sTexEmissive), - sTexShininess(other.sTexShininess), - mBumpHeight(other.mBumpHeight), - mEmissive(other.mEmissive), - sTexAmbient(other.sTexAmbient), - mTwoSided(other.mTwoSided) { - // empty - } + Material(const Material &other) = default; - //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it - Material(Material &&other) AI_NO_EXCEPT : mName(std::move(other.mName)), - mDiffuse(other.mDiffuse), - mSpecularExponent(other.mSpecularExponent), - mShininessStrength(other.mShininessStrength), - mSpecular(other.mSpecular), - mAmbient(other.mAmbient), - mShading(other.mShading), - mTransparency(other.mTransparency), - sTexDiffuse(std::move(other.sTexDiffuse)), - sTexOpacity(std::move(other.sTexOpacity)), - sTexSpecular(std::move(other.sTexSpecular)), - sTexReflective(std::move(other.sTexReflective)), - sTexBump(std::move(other.sTexBump)), - sTexEmissive(std::move(other.sTexEmissive)), - sTexShininess(std::move(other.sTexShininess)), - mBumpHeight(other.mBumpHeight), - mEmissive(other.mEmissive), - sTexAmbient(std::move(other.sTexAmbient)), - mTwoSided(other.mTwoSided) { - // empty - } - - Material &operator=(Material &&other) AI_NO_EXCEPT { - if (this == &other) { - return *this; - } - - mName = std::move(other.mName); - mDiffuse = other.mDiffuse; - mSpecularExponent = other.mSpecularExponent; - mShininessStrength = other.mShininessStrength, - mSpecular = other.mSpecular; - mAmbient = other.mAmbient; - mShading = other.mShading; - mTransparency = other.mTransparency; - sTexDiffuse = std::move(other.sTexDiffuse); - sTexOpacity = std::move(other.sTexOpacity); - sTexSpecular = std::move(other.sTexSpecular); - sTexReflective = std::move(other.sTexReflective); - sTexBump = std::move(other.sTexBump); - sTexEmissive = std::move(other.sTexEmissive); - sTexShininess = std::move(other.sTexShininess); - mBumpHeight = other.mBumpHeight; - mEmissive = other.mEmissive; - sTexAmbient = std::move(other.sTexAmbient); - mTwoSided = other.mTwoSided; - - return *this; - } - - virtual ~Material() { - // empty - } + virtual ~Material() = default; //! Name of the material std::string mName; diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.cpp b/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.cpp index 769e8a6ee..3317017be 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -54,9 +54,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Discreet 3DS Importer", "", "", @@ -103,10 +103,6 @@ Discreet3DSImporter::Discreet3DSImporter() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -Discreet3DSImporter::~Discreet3DSImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool Discreet3DSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -266,8 +262,15 @@ void Discreet3DSImporter::ParseMainChunk() { }; ASSIMP_3DS_END_CHUNK(); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code-return" +#endif // recursively continue processing this hierarchy level return ParseMainChunk(); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif } // ------------------------------------------------------------------------------------------------ @@ -362,7 +365,7 @@ void Discreet3DSImporter::ParseChunk(const char *name, unsigned int num) { // IMPLEMENTATION NOTE; // Cameras or lights define their transformation in their parent node and in the // corresponding light or camera chunks. However, we read and process the latter - // to to be able to return valid cameras/lights even if no scenegraph is given. + // to be able to return valid cameras/lights even if no scenegraph is given. // get chunk type switch (chunk.Flag) { @@ -1332,4 +1335,6 @@ void Discreet3DSImporter::ParseColorChunk(aiColor3D *out, bool acceptPercent) { (void)bGamma; } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.h b/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.h index f47fcfef9..1d6953e29 100644 --- a/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.h +++ b/Engine/lib/assimp/code/AssetLib/3DS/3DSLoader.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,7 +59,6 @@ struct aiNode; namespace Assimp { - using namespace D3DS; // --------------------------------------------------------------------------------- @@ -68,7 +67,7 @@ using namespace D3DS; class Discreet3DSImporter : public BaseImporter { public: Discreet3DSImporter(); - ~Discreet3DSImporter(); + ~Discreet3DSImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/3MF/3MFTypes.h b/Engine/lib/assimp/code/AssetLib/3MF/3MFTypes.h index 987cdf613..57d2b281a 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/3MFTypes.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/3MFTypes.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,6 +57,7 @@ enum class ResourceType { RT_BaseMaterials, RT_EmbeddedTexture2D, RT_Texture2DGroup, + RT_ColorGroup, RT_Unknown }; // To be extended with other resource types (eg. material extension resources like Texture2d, Texture2dGroup...) @@ -69,9 +70,7 @@ public: // empty } - virtual ~Resource() { - // empty - } + virtual ~Resource() = default; virtual ResourceType getType() const { return ResourceType::RT_Unknown; @@ -95,7 +94,7 @@ public: // empty } - ~EmbeddedTexture() = default; + ~EmbeddedTexture() override = default; ResourceType getType() const override { return ResourceType::RT_EmbeddedTexture2D; @@ -112,13 +111,28 @@ public: // empty } - ~Texture2DGroup() = default; + ~Texture2DGroup() override = default; ResourceType getType() const override { return ResourceType::RT_Texture2DGroup; } }; +class ColorGroup : public Resource { +public: + std::vector mColors; + ColorGroup(int id) : + Resource(id){ + // empty + } + + ~ColorGroup() override = default; + + ResourceType getType() const override { + return ResourceType::RT_ColorGroup; + } +}; + class BaseMaterials : public Resource { public: std::vector mMaterialIndex; @@ -129,7 +143,7 @@ public: // empty } - ~BaseMaterials() = default; + ~BaseMaterials() override = default; ResourceType getType() const override { return ResourceType::RT_BaseMaterials; @@ -154,7 +168,7 @@ public: // empty } - ~Object() = default; + ~Object() override = default; ResourceType getType() const override { return ResourceType::RT_Object; diff --git a/Engine/lib/assimp/code/AssetLib/3MF/3MFXmlTags.h b/Engine/lib/assimp/code/AssetLib/3MF/3MFXmlTags.h index 333d169aa..aea66667b 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/3MFXmlTags.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/3MFXmlTags.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -98,6 +98,11 @@ namespace XmlTag { const char *const texture_cuurd_u = "u"; const char *const texture_cuurd_v = "v"; + // vertex color definitions + const char *const colorgroup = "m:colorgroup"; + const char *const color_item = "m:color"; + const char *const color_vaule = "color"; + // Meta info tags const char* const CONTENT_TYPES_ARCHIVE = "[Content_Types].xml"; const char* const ROOT_RELATIONSHIPS_ARCHIVE = "_rels/.rels"; diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.cpp b/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.cpp index 42cd991e6..6c09f097d 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -83,7 +83,7 @@ void ExportScene3MF(const char *pFile, IOSystem *pIOSystem, const aiScene *pScen namespace D3MF { D3MFExporter::D3MFExporter(const char *pFile, const aiScene *pScene) : - mArchiveName(pFile), m_zipArchive(nullptr), mScene(pScene), mModelOutput(), mRelOutput(), mContentOutput(), mBuildItems(), mRelations() { + mArchiveName(pFile), m_zipArchive(nullptr), mScene(pScene) { // empty } @@ -249,10 +249,10 @@ void D3MFExporter::writeBaseMaterials() { if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) { hexDiffuseColor = ai_rgba2hex( - (int)((ai_real)color.r) * 255, - (int)((ai_real)color.g) * 255, - (int)((ai_real)color.b) * 255, - (int)((ai_real)color.a) * 255, + (int)(((ai_real)color.r) * 255), + (int)(((ai_real)color.g) * 255), + (int)(((ai_real)color.b) * 255), + (int)(((ai_real)color.a) * 255), true); } else { diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.h b/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.h index 680d54f91..6be0c32ca 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.cpp b/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.cpp index 5d9644fa5..987cdd492 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,7 +68,7 @@ namespace Assimp { using namespace D3MF; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "3mf Importer", "", "", @@ -81,16 +81,17 @@ static const aiImporterDesc desc = { "3mf" }; -D3MFImporter::D3MFImporter() = default; - -D3MFImporter::~D3MFImporter() = default; - -bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool /*checkSig*/) const { +bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool ) const { if (!ZipArchiveIOSystem::isZipArchive(pIOHandler, filename)) { return false; } - D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename); - return opcPackage.validate(); + static const char *const ModelRef = "3D/3dmodel.model"; + ZipArchiveIOSystem archive(pIOHandler, filename); + if (!archive.Exists(ModelRef)) { + return false; + } + + return true; } void D3MFImporter::SetupProperties(const Importer*) { diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.h b/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.h index a39ae790f..82a1546cd 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,10 +56,10 @@ namespace Assimp { class D3MFImporter : public BaseImporter { public: /// @brief The default class constructor. - D3MFImporter(); + D3MFImporter() = default; /// @brief The class destructor. - ~D3MFImporter() override; + ~D3MFImporter() override = default; /// @brief Performs the data format detection. /// @param pFile The filename to check. diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp b/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp index a2182dc29..be0615904 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,7 +68,7 @@ using OpcPackageRelationshipPtr = std::shared_ptr; class OpcPackageRelationshipReader { public: OpcPackageRelationshipReader(XmlParser &parser) : - m_relationShips() { + mRelations() { XmlNode root = parser.getRootNode(); ParseRootNode(root); } @@ -108,20 +108,20 @@ public: relPtr->type = currentNode.attribute(XmlTag::RELS_ATTRIB_TYPE).as_string(); relPtr->target = currentNode.attribute(XmlTag::RELS_ATTRIB_TARGET).as_string(); if (validateRels(relPtr)) { - m_relationShips.push_back(relPtr); + mRelations.push_back(relPtr); } } } } - std::vector m_relationShips; + std::vector mRelations; }; static bool IsEmbeddedTexture( const std::string &filename ) { const std::string extension = BaseImporter::GetExtension(filename); - if (extension == "jpg" || extension == "png") { + if (extension == "jpg" || extension == "png" || extension == "jpeg") { std::string::size_type pos = filename.find("thumbnail"); - if (pos == std::string::npos) { + if (pos != std::string::npos) { return false; } return true; @@ -160,7 +160,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : // deal with zip-bug rootFile = rootFile.substr(1); } - } + } ASSIMP_LOG_VERBOSE_DEBUG(rootFile); @@ -186,9 +186,6 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : D3MFOpcPackage::~D3MFOpcPackage() { mZipArchive->Close(mRootStream); delete mZipArchive; - for (auto tex : mEmbeddedTextures) { - delete tex; - } } IOStream *D3MFOpcPackage::RootStream() const { @@ -217,11 +214,11 @@ std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream *stream) { OpcPackageRelationshipReader reader(xmlParser); - auto itr = std::find_if(reader.m_relationShips.begin(), reader.m_relationShips.end(), [](const OpcPackageRelationshipPtr &rel) { + auto itr = std::find_if(reader.mRelations.begin(), reader.mRelations.end(), [](const OpcPackageRelationshipPtr &rel) { return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; }); - if (itr == reader.m_relationShips.end()) { + if (itr == reader.mRelations.end()) { throw DeadlyImportError("Cannot find ", XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE); } diff --git a/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.h b/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.h index f6803a0ef..9782752bf 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/D3MFOpcPackage.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.cpp b/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.cpp index 043ac84cc..fdc9f5a3d 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.cpp +++ b/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -44,19 +44,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "3MFTypes.h" #include +#include + namespace Assimp { namespace D3MF { -static const int IdNotSet = -1; +static constexpr int IdNotSet = -1; namespace { -static const size_t ColRGBA_Len = 9; -static const size_t ColRGB_Len = 7; +static constexpr size_t ColRGBA_Len = 9; +static constexpr size_t ColRGB_Len = 7; // format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) -bool validateColorString(const char *color) { - const size_t len = strlen(color); +bool validateColorString(const std::string color) { + const size_t len = color.size(); if (ColRGBA_Len != len && ColRGB_Len != len) { return false; } @@ -73,7 +75,7 @@ aiFace ReadTriangle(XmlNode &node, int &texId0, int &texId1, int &texId2) { face.mIndices[1] = static_cast(std::atoi(node.attribute(XmlTag::v2).as_string())); face.mIndices[2] = static_cast(std::atoi(node.attribute(XmlTag::v3).as_string())); - texId0 = texId1 = texId2 = -1; + texId0 = texId1 = texId2 = IdNotSet; XmlParser::getIntAttribute(node, XmlTag::p1, texId0); XmlParser::getIntAttribute(node, XmlTag::p2, texId1); XmlParser::getIntAttribute(node, XmlTag::p3, texId2); @@ -155,8 +157,8 @@ aiMatrix4x4 parseTransformMatrix(const std::string& matrixStr) { return transformMatrix; } -bool parseColor(const char *color, aiColor4D &diffuse) { - if (nullptr == color) { +bool parseColor(const std::string &color, aiColor4D &diffuse) { + if (color.empty()) { return false; } @@ -176,7 +178,7 @@ bool parseColor(const char *color, aiColor4D &diffuse) { char b[3] = { color[5], color[6], '\0' }; diffuse.b = static_cast(strtol(b, nullptr, 16)) / ai_real(255.0); - const size_t len = strlen(color); + const size_t len = color.size(); if (ColRGB_Len == len) { return true; } @@ -214,7 +216,7 @@ void XmlSerializer::ImportXml(aiScene *scene) { if (nullptr == scene) { return; } - + scene->mRootNode = new aiNode(XmlTag::RootTag); XmlNode node = mXmlParser->getRootNode().child(XmlTag::model); if (node.empty()) { @@ -234,6 +236,8 @@ void XmlSerializer::ImportXml(aiScene *scene) { ReadBaseMaterials(currentNode); } else if (currentNodeName == XmlTag::meta) { ReadMetadata(currentNode); + } else if (currentNodeName == XmlTag::colorgroup) { + ReadColorGroup(currentNode); } } StoreMaterialsInScene(scene); @@ -329,9 +333,49 @@ void XmlSerializer::ReadObject(XmlNode &node) { if (hasPid) { auto it = mResourcesDictionnary.find(pid); - if (hasPindex && it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_BaseMaterials) { - BaseMaterials *materials = static_cast(it->second); - mesh->mMaterialIndex = materials->mMaterialIndex[pindex]; + if (hasPindex && it != mResourcesDictionnary.end()) { + if (it->second->getType() == ResourceType::RT_BaseMaterials) { + BaseMaterials *materials = static_cast(it->second); + mesh->mMaterialIndex = materials->mMaterialIndex[pindex]; + } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) { + Texture2DGroup *group = static_cast(it->second); + if (mesh->mTextureCoords[0] == nullptr) { + mesh->mNumUVComponents[0] = 2; + for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + mesh->mNumUVComponents[i] = 0; + } + + const std::string name = ai_to_string(group->mTexId); + for (size_t i = 0; i < mMaterials.size(); ++i) { + if (name == mMaterials[i]->GetName().C_Str()) { + mesh->mMaterialIndex = static_cast(i); + } + } + + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) { + mesh->mTextureCoords[0][vertex_idx] = + aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f); + } + } else { + for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) { + if (mesh->mTextureCoords[0][vertex_idx].z < 0) { + // use default + mesh->mTextureCoords[0][vertex_idx] = + aiVector3D(group->mTex2dCoords[pindex].x, group->mTex2dCoords[pindex].y, 0.0f); + } + } + } + }else if (it->second->getType() == ResourceType::RT_ColorGroup) { + if (mesh->mColors[0] == nullptr) { + mesh->mColors[0] = new aiColor4D[mesh->mNumVertices]; + + ColorGroup *group = static_cast(it->second); + for (unsigned int vertex_idx = 0; vertex_idx < mesh->mNumVertices; vertex_idx++) { + mesh->mColors[0][vertex_idx] = group->mColors[pindex]; + } + } + } } } @@ -413,27 +457,36 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) { for (XmlNode ¤tNode : node.children()) { const std::string currentName = currentNode.name(); if (currentName == XmlTag::triangle) { - int pid = IdNotSet, p1 = IdNotSet; + int pid = IdNotSet; bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid); - bool hasP1 = getNodeAttribute(currentNode, D3MF::XmlTag::p1, p1); - int texId[3]; - Texture2DGroup *group = nullptr; - aiFace face = ReadTriangle(currentNode, texId[0], texId[1], texId[2]); - if (hasPid && hasP1) { + int pindex[3]; + aiFace face = ReadTriangle(currentNode, pindex[0], pindex[1], pindex[2]); + if (hasPid && (pindex[0] != IdNotSet || pindex[1] != IdNotSet || pindex[2] != IdNotSet)) { auto it = mResourcesDictionnary.find(pid); if (it != mResourcesDictionnary.end()) { if (it->second->getType() == ResourceType::RT_BaseMaterials) { BaseMaterials *baseMaterials = static_cast(it->second); - mesh->mMaterialIndex = baseMaterials->mMaterialIndex[p1]; + + auto update_material = [&](int idx) { + if (pindex[idx] != IdNotSet) { + mesh->mMaterialIndex = baseMaterials->mMaterialIndex[pindex[idx]]; + } + }; + + update_material(0); + update_material(1); + update_material(2); + } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) { + // Load texture coordinates into mesh, when any + Texture2DGroup *group = static_cast(it->second); // fix bug if (mesh->mTextureCoords[0] == nullptr) { mesh->mNumUVComponents[0] = 2; for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { mesh->mNumUVComponents[i] = 0; } - group = static_cast(it->second); const std::string name = ai_to_string(group->mTexId); for (size_t i = 0; i < mMaterials.size(); ++i) { if (name == mMaterials[i]->GetName().C_Str()) { @@ -441,19 +494,42 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) { } } mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + for (unsigned int vertex_index = 0; vertex_index < mesh->mNumVertices; vertex_index++) { + mesh->mTextureCoords[0][vertex_index].z = IdNotSet;//mark not set + } } - } - } - } - // Load texture coordinates into mesh, when any - if (group != nullptr) { - size_t i0 = face.mIndices[0]; - size_t i1 = face.mIndices[1]; - size_t i2 = face.mIndices[2]; - mesh->mTextureCoords[0][i0] = aiVector3D(group->mTex2dCoords[texId[0]].x, group->mTex2dCoords[texId[0]].y, 0.0f); - mesh->mTextureCoords[0][i1] = aiVector3D(group->mTex2dCoords[texId[1]].x, group->mTex2dCoords[texId[1]].y, 0.0f); - mesh->mTextureCoords[0][i2] = aiVector3D(group->mTex2dCoords[texId[2]].x, group->mTex2dCoords[texId[2]].y, 0.0f); + auto update_texture = [&](int idx) { + if (pindex[idx] != IdNotSet) { + size_t vertex_index = face.mIndices[idx]; + mesh->mTextureCoords[0][vertex_index] = + aiVector3D(group->mTex2dCoords[pindex[idx]].x, group->mTex2dCoords[pindex[idx]].y, 0.0f); + } + }; + + update_texture(0); + update_texture(1); + update_texture(2); + + } else if (it->second->getType() == ResourceType::RT_ColorGroup) { + // Load vertex color into mesh, when any + ColorGroup *group = static_cast(it->second); + if (mesh->mColors[0] == nullptr) { + mesh->mColors[0] = new aiColor4D[mesh->mNumVertices]; + } + + auto update_color = [&](int idx) { + if (pindex[idx] != IdNotSet) { + size_t vertex_index = face.mIndices[idx]; + mesh->mColors[0][vertex_index] = group->mColors[pindex[idx]]; + } + }; + + update_color(0); + update_color(1); + update_color(2); + } + } } faces.push_back(face); @@ -582,7 +658,7 @@ aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basemater stdMaterialName += strId; stdMaterialName += "_"; if (hasName) { - stdMaterialName += std::string(name); + stdMaterialName += name; } else { stdMaterialName += "basemat_"; stdMaterialName += ai_to_string(mMaterials.size()); @@ -596,6 +672,38 @@ aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basemater return material; } +void XmlSerializer::ReadColor(XmlNode &node, ColorGroup *colorGroup) { + if (node.empty() || nullptr == colorGroup) { + return; + } + + for (XmlNode currentNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::color_item) { + const char *color = currentNode.attribute(XmlTag::color_vaule).as_string(); + aiColor4D color_value; + if (parseColor(color, color_value)) { + colorGroup->mColors.push_back(color_value); + } + } + } +} + +void XmlSerializer::ReadColorGroup(XmlNode &node) { + if (node.empty()) { + return; + } + + int id = IdNotSet; + if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) { + return; + } + + ColorGroup *group = new ColorGroup(id); + ReadColor(node, group); + mResourcesDictionnary.insert(std::make_pair(id, group)); +} + void XmlSerializer::StoreMaterialsInScene(aiScene *scene) { if (nullptr == scene) { return; diff --git a/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.h b/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.h index 6cf6a70a9..d0700a631 100644 --- a/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.h +++ b/Engine/lib/assimp/code/AssetLib/3MF/XmlSerializer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,6 +57,7 @@ class D3MFOpcPackage; class Object; class Texture2DGroup; class EmbeddedTexture; +class ColorGroup; class XmlSerializer { public: @@ -78,6 +79,8 @@ private: void ReadTextureGroup(XmlNode &node); aiMaterial *readMaterialDef(XmlNode &node, unsigned int basematerialsId); void StoreMaterialsInScene(aiScene *scene); + void ReadColorGroup(XmlNode &node); + void ReadColor(XmlNode &node, ColorGroup *colorGroup); private: struct MetaEntry { diff --git a/Engine/lib/assimp/code/AssetLib/AC/ACLoader.cpp b/Engine/lib/assimp/code/AssetLib/AC/ACLoader.cpp index 26bc2e9d5..242364150 100644 --- a/Engine/lib/assimp/code/AssetLib/AC/ACLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/AC/ACLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,9 +60,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "AC3D Importer", "", "", @@ -77,8 +77,8 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // skip to the next token -inline const char *AcSkipToNextToken(const char *buffer) { - if (!SkipSpaces(&buffer)) { +inline const char *AcSkipToNextToken(const char *buffer, const char *end) { + if (!SkipSpaces(&buffer, end)) { ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL"); } return buffer; @@ -86,13 +86,13 @@ inline const char *AcSkipToNextToken(const char *buffer) { // ------------------------------------------------------------------------------------------------ // read a string (may be enclosed in double quotation marks). buffer must point to " -inline const char *AcGetString(const char *buffer, std::string &out) { +inline const char *AcGetString(const char *buffer, const char *end, std::string &out) { if (*buffer == '\0') { throw DeadlyImportError("AC3D: Unexpected EOF in string"); } ++buffer; const char *sz = buffer; - while ('\"' != *buffer) { + while ('\"' != *buffer && buffer != end) { if (IsLineEnd(*buffer)) { ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string"); out = "ERROR"; @@ -112,8 +112,8 @@ inline const char *AcGetString(const char *buffer, std::string &out) { // ------------------------------------------------------------------------------------------------ // read 1 to n floats prefixed with an optional predefined identifier template -inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *name, size_t name_length, size_t num, T *out) { - buffer = AcSkipToNextToken(buffer); +inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, const char *name, size_t name_length, size_t num, T *out) { + buffer = AcSkipToNextToken(buffer, end); if (0 != name_length) { if (0 != strncmp(buffer, name, name_length) || !IsSpace(buffer[name_length])) { ASSIMP_LOG_ERROR("AC3D: Unexpected token. ", name, " was expected."); @@ -122,7 +122,7 @@ inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *name buffer += name_length + 1; } for (unsigned int _i = 0; _i < num; ++_i) { - buffer = AcSkipToNextToken(buffer); + buffer = AcSkipToNextToken(buffer, end); buffer = fast_atoreal_move(buffer, ((float *)out)[_i]); } @@ -132,7 +132,7 @@ inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *name // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer AC3DImporter::AC3DImporter() : - buffer(), + mBuffer(), configSplitBFCull(), configEvalSubdivision(), mNumMeshes(), @@ -164,17 +164,17 @@ const aiImporterDesc *AC3DImporter::GetInfo() const { // ------------------------------------------------------------------------------------------------ // Get a pointer to the next line from the file bool AC3DImporter::GetNextLine() { - SkipLine(&buffer); - return SkipSpaces(&buffer); + SkipLine(&mBuffer.data, mBuffer.end); + return SkipSpaces(&mBuffer.data, mBuffer.end); } // ------------------------------------------------------------------------------------------------ // Parse an object section in an AC file -void AC3DImporter::LoadObjectSection(std::vector &objects) { - if (!TokenMatch(buffer, "OBJECT", 6)) - return; +bool AC3DImporter::LoadObjectSection(std::vector &objects) { + if (!TokenMatch(mBuffer.data, "OBJECT", 6)) + return false; - SkipSpaces(&buffer); + SkipSpaces(&mBuffer.data, mBuffer.end); ++mNumMeshes; @@ -182,7 +182,7 @@ void AC3DImporter::LoadObjectSection(std::vector &objects) { Object &obj = objects.back(); aiLight *light = nullptr; - if (!ASSIMP_strincmp(buffer, "light", 5)) { + if (!ASSIMP_strincmp(mBuffer.data, "light", 5)) { // This is a light source. Add it to the list mLights->push_back(light = new aiLight()); @@ -193,65 +193,71 @@ void AC3DImporter::LoadObjectSection(std::vector &objects) { // Generate a default name for both the light source and the node // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version. - light->mName.length = ::ai_snprintf(light->mName.data, MAXLEN, "ACLight_%i", static_cast(mLights->size()) - 1); + light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast(mLights->size()) - 1); obj.name = std::string(light->mName.data); ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered"); obj.type = Object::Light; - } else if (!ASSIMP_strincmp(buffer, "group", 5)) { + } else if (!ASSIMP_strincmp(mBuffer.data, "group", 5)) { obj.type = Object::Group; - } else if (!ASSIMP_strincmp(buffer, "world", 5)) { + } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) { obj.type = Object::World; } else obj.type = Object::Poly; while (GetNextLine()) { - if (TokenMatch(buffer, "kids", 4)) { - SkipSpaces(&buffer); - unsigned int num = strtoul10(buffer, &buffer); + if (TokenMatch(mBuffer.data, "kids", 4)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + unsigned int num = strtoul10(mBuffer.data, &mBuffer.data); GetNextLine(); if (num) { // load the children of this object recursively obj.children.reserve(num); - for (unsigned int i = 0; i < num; ++i) - LoadObjectSection(obj.children); + for (unsigned int i = 0; i < num; ++i) { + if (!LoadObjectSection(obj.children)) { + ASSIMP_LOG_WARN("AC3D: wrong number of kids"); + break; + } + } } - return; - } else if (TokenMatch(buffer, "name", 4)) { - SkipSpaces(&buffer); - buffer = AcGetString(buffer, obj.name); + return true; + } else if (TokenMatch(mBuffer.data, "name", 4)) { + SkipSpaces(&mBuffer.data, mBuffer.data); + mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, obj.name); // If this is a light source, we'll also need to store // the name of the node in it. if (light) { light->mName.Set(obj.name); } - } else if (TokenMatch(buffer, "texture", 7)) { - SkipSpaces(&buffer); - buffer = AcGetString(buffer, obj.texture); - } else if (TokenMatch(buffer, "texrep", 6)) { - SkipSpaces(&buffer); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &obj.texRepeat); + } else if (TokenMatch(mBuffer.data, "texture", 7)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + std::string texture; + mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, texture); + obj.textures.push_back(texture); + } else if (TokenMatch(mBuffer.data, "texrep", 6)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texRepeat); if (!obj.texRepeat.x || !obj.texRepeat.y) obj.texRepeat = aiVector2D(1.f, 1.f); - } else if (TokenMatch(buffer, "texoff", 6)) { - SkipSpaces(&buffer); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &obj.texOffset); - } else if (TokenMatch(buffer, "rot", 3)) { - SkipSpaces(&buffer); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 9, &obj.rotation); - } else if (TokenMatch(buffer, "loc", 3)) { - SkipSpaces(&buffer); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 3, &obj.translation); - } else if (TokenMatch(buffer, "subdiv", 6)) { - SkipSpaces(&buffer); - obj.subDiv = strtoul10(buffer, &buffer); - } else if (TokenMatch(buffer, "crease", 6)) { - SkipSpaces(&buffer); - obj.crease = fast_atof(buffer); - } else if (TokenMatch(buffer, "numvert", 7)) { - SkipSpaces(&buffer); + } else if (TokenMatch(mBuffer.data, "texoff", 6)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &obj.texOffset); + } else if (TokenMatch(mBuffer.data, "rot", 3)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 9, &obj.rotation); + } else if (TokenMatch(mBuffer.data, "loc", 3)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &obj.translation); + } else if (TokenMatch(mBuffer.data, "subdiv", 6)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + obj.subDiv = strtoul10(mBuffer.data, &mBuffer.data); + } else if (TokenMatch(mBuffer.data, "crease", 6)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + obj.crease = fast_atof(mBuffer.data); + } else if (TokenMatch(mBuffer.data, "numvert", 7)) { + SkipSpaces(&mBuffer.data, mBuffer.end); - unsigned int t = strtoul10(buffer, &buffer); + unsigned int t = strtoul10(mBuffer.data, &mBuffer.data); if (t >= AI_MAX_ALLOC(aiVector3D)) { throw DeadlyImportError("AC3D: Too many vertices, would run out of memory"); } @@ -260,59 +266,59 @@ void AC3DImporter::LoadObjectSection(std::vector &objects) { if (!GetNextLine()) { ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet"); break; - } else if (!IsNumeric(*buffer)) { + } else if (!IsNumeric(*mBuffer.data)) { ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet"); - --buffer; // make sure the line is processed a second time + --mBuffer.data; // make sure the line is processed a second time break; } obj.vertices.emplace_back(); aiVector3D &v = obj.vertices.back(); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 3, &v.x); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 3, &v.x); } - } else if (TokenMatch(buffer, "numsurf", 7)) { - SkipSpaces(&buffer); + } else if (TokenMatch(mBuffer.data, "numsurf", 7)) { + SkipSpaces(&mBuffer.data, mBuffer.end); bool Q3DWorkAround = false; - const unsigned int t = strtoul10(buffer, &buffer); + const unsigned int t = strtoul10(mBuffer.data, &mBuffer.data); obj.surfaces.reserve(t); for (unsigned int i = 0; i < t; ++i) { GetNextLine(); - if (!TokenMatch(buffer, "SURF", 4)) { + if (!TokenMatch(mBuffer.data, "SURF", 4)) { // FIX: this can occur for some files - Quick 3D for // example writes no surf chunks if (!Q3DWorkAround) { ASSIMP_LOG_WARN("AC3D: SURF token was expected"); ASSIMP_LOG_VERBOSE_DEBUG("Continuing with Quick3D Workaround enabled"); } - --buffer; // make sure the line is processed a second time + --mBuffer.data; // make sure the line is processed a second time // break; --- see fix notes above Q3DWorkAround = true; } - SkipSpaces(&buffer); + SkipSpaces(&mBuffer.data, mBuffer.end); obj.surfaces.emplace_back(); Surface &surf = obj.surfaces.back(); - surf.flags = strtoul_cppstyle(buffer); + surf.flags = strtoul_cppstyle(mBuffer.data); - while (1) { + while (true) { if (!GetNextLine()) { throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete"); } - if (TokenMatch(buffer, "mat", 3)) { - SkipSpaces(&buffer); - surf.mat = strtoul10(buffer); - } else if (TokenMatch(buffer, "refs", 4)) { + if (TokenMatch(mBuffer.data, "mat", 3)) { + SkipSpaces(&mBuffer.data, mBuffer.end); + surf.mat = strtoul10(mBuffer.data); + } else if (TokenMatch(mBuffer.data, "refs", 4)) { // --- see fix notes above if (Q3DWorkAround) { if (!surf.entries.empty()) { - buffer -= 6; + mBuffer.data -= 6; break; } } - SkipSpaces(&buffer); - const unsigned int m = strtoul10(buffer); + SkipSpaces(&mBuffer.data, mBuffer.end); + const unsigned int m = strtoul10(mBuffer.data); surf.entries.reserve(m); obj.numRefs += m; @@ -325,12 +331,12 @@ void AC3DImporter::LoadObjectSection(std::vector &objects) { surf.entries.emplace_back(); Surface::SurfaceEntry &entry = surf.entries.back(); - entry.first = strtoul10(buffer, &buffer); - SkipSpaces(&buffer); - buffer = TAcCheckedLoadFloatArray(buffer, "", 0, 2, &entry.second); + entry.first = strtoul10(mBuffer.data, &mBuffer.data); + SkipSpaces(&mBuffer.data, mBuffer.end); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "", 0, 2, &entry.second); } } else { - --buffer; // make sure the line is processed a second time + --mBuffer.data; // make sure the line is processed a second time break; } } @@ -338,6 +344,7 @@ void AC3DImporter::LoadObjectSection(std::vector &objects) { } } ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected"); + return false; } // ------------------------------------------------------------------------------------------------ @@ -351,8 +358,8 @@ void AC3DImporter::ConvertMaterial(const Object &object, s.Set(matSrc.name); matDest.AddProperty(&s, AI_MATKEY_NAME); } - if (object.texture.length()) { - s.Set(object.texture); + if (!object.textures.empty()) { + s.Set(object.textures[0]); matDest.AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); // UV transformation @@ -443,7 +450,7 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, idx = 0; } if ((*it).entries.empty()) { - ASSIMP_LOG_WARN("AC3D: surface her zero vertex references"); + ASSIMP_LOG_WARN("AC3D: surface has zero vertex references"); } // validate all vertex indices to make sure we won't crash here @@ -461,16 +468,15 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, } switch ((*it).GetType()) { - // closed line - case Surface::ClosedLine: - needMat[idx].first += (unsigned int)(*it).entries.size(); - needMat[idx].second += (unsigned int)(*it).entries.size() << 1u; + case Surface::ClosedLine: // closed line + needMat[idx].first += static_cast((*it).entries.size()); + needMat[idx].second += static_cast((*it).entries.size() << 1u); break; // unclosed line case Surface::OpenLine: - needMat[idx].first += (unsigned int)(*it).entries.size() - 1; - needMat[idx].second += ((unsigned int)(*it).entries.size() - 1) << 1u; + needMat[idx].first += static_cast((*it).entries.size() - 1); + needMat[idx].second += static_cast(((*it).entries.size() - 1) << 1u); break; // triangle strip @@ -532,7 +538,7 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, // allocate UV coordinates, but only if the texture name for the // surface is not empty aiVector3D *uv = nullptr; - if (object.texture.length()) { + if (!object.textures.empty()) { uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; mesh->mNumUVComponents[0] = 2; } @@ -572,15 +578,6 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, const Surface::SurfaceEntry &entry2 = src.entries[i + 1]; const Surface::SurfaceEntry &entry3 = src.entries[i + 2]; - // skip degenerate triangles - if (object.vertices[entry1.first] == object.vertices[entry2.first] || - object.vertices[entry1.first] == object.vertices[entry3.first] || - object.vertices[entry2.first] == object.vertices[entry3.first]) { - mesh->mNumFaces--; - mesh->mNumVertices -= 3; - continue; - } - aiFace &face = *faces++; face.mNumIndices = 3; face.mIndices = new unsigned int[face.mNumIndices]; @@ -699,18 +696,18 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, // generate a name depending on the type of the node switch (object.type) { case Object::Group: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACGroup_%i", mGroupsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACGroup_%i", mGroupsCounter++); break; case Object::Poly: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACPoly_%i", mPolysCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACPoly_%i", mPolysCounter++); break; case Object::Light: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACLight_%i", mLightsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACLight_%i", mLightsCounter++); break; // there shouldn't be more than one world, but we don't care case Object::World: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACWorld_%i", mWorldsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++); break; } } @@ -750,7 +747,7 @@ void AC3DImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open AC3D file ", pFile, "."); } @@ -758,17 +755,18 @@ void AC3DImporter::InternReadFile(const std::string &pFile, std::vector mBuffer2; TextFileToBuffer(file.get(), mBuffer2); - buffer = &mBuffer2[0]; + mBuffer.data = &mBuffer2[0]; + mBuffer.end = &mBuffer2[0] + mBuffer2.size(); mNumMeshes = 0; mLightsCounter = mPolysCounter = mWorldsCounter = mGroupsCounter = 0; - if (::strncmp(buffer, "AC3D", 4)) { + if (::strncmp(mBuffer.data, "AC3D", 4)) { throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found"); } // print the file format version to the console - unsigned int version = HexDigitToDecimal(buffer[4]); + unsigned int version = HexDigitToDecimal(mBuffer.data[4]); char msg[3]; ASSIMP_itoa10(msg, 3, version); ASSIMP_LOG_INFO("AC3D file format version: ", msg); @@ -783,30 +781,31 @@ void AC3DImporter::InternReadFile(const std::string &pFile, mLights = &lights; while (GetNextLine()) { - if (TokenMatch(buffer, "MATERIAL", 8)) { + if (TokenMatch(mBuffer.data, "MATERIAL", 8)) { materials.emplace_back(); Material &mat = materials.back(); // manually parse the material ... sscanf would use the buldin atof ... // Format: (name) rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f - buffer = AcSkipToNextToken(buffer); - if ('\"' == *buffer) { - buffer = AcGetString(buffer, mat.name); - buffer = AcSkipToNextToken(buffer); + mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end); + if ('\"' == *mBuffer.data) { + mBuffer.data = AcGetString(mBuffer.data, mBuffer.end, mat.name); + mBuffer.data = AcSkipToNextToken(mBuffer.data, mBuffer.end); } - buffer = TAcCheckedLoadFloatArray(buffer, "rgb", 3, 3, &mat.rgb); - buffer = TAcCheckedLoadFloatArray(buffer, "amb", 3, 3, &mat.amb); - buffer = TAcCheckedLoadFloatArray(buffer, "emis", 4, 3, &mat.emis); - buffer = TAcCheckedLoadFloatArray(buffer, "spec", 4, 3, &mat.spec); - buffer = TAcCheckedLoadFloatArray(buffer, "shi", 3, 1, &mat.shin); - buffer = TAcCheckedLoadFloatArray(buffer, "trans", 5, 1, &mat.trans); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "rgb", 3, 3, &mat.rgb); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "amb", 3, 3, &mat.amb); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "emis", 4, 3, &mat.emis); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "spec", 4, 3, &mat.spec); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "shi", 3, 1, &mat.shin); + mBuffer.data = TAcCheckedLoadFloatArray(mBuffer.data, mBuffer.end, "trans", 5, 1, &mat.trans); + } else { + LoadObjectSection(rootObjects); } - LoadObjectSection(rootObjects); } - if (rootObjects.empty() || !mNumMeshes) { + if (rootObjects.empty() || mNumMeshes == 0u) { throw DeadlyImportError("AC3D: No meshes have been loaded"); } if (materials.empty()) { @@ -822,7 +821,7 @@ void AC3DImporter::InternReadFile(const std::string &pFile, materials.reserve(mNumMeshes); // generate a dummy root if there are multiple objects on the top layer - Object *root; + Object *root = nullptr; if (1 == rootObjects.size()) root = &rootObjects[0]; else { @@ -835,7 +834,7 @@ void AC3DImporter::InternReadFile(const std::string &pFile, delete root; } - if (!::strncmp(pScene->mRootNode->mName.data, "Node", 4)) { + if (::strncmp(pScene->mRootNode->mName.data, "Node", 4) == 0) { pScene->mRootNode->mName.Set(""); } @@ -854,10 +853,12 @@ void AC3DImporter::InternReadFile(const std::string &pFile, // copy lights pScene->mNumLights = (unsigned int)lights.size(); - if (lights.size()) { + if (!lights.empty()) { pScene->mLights = new aiLight *[lights.size()]; ::memcpy(pScene->mLights, &lights[0], lights.size() * sizeof(void *)); } } +} // namespace Assimp + #endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/AC/ACLoader.h b/Engine/lib/assimp/code/AssetLib/AC/ACLoader.h index aabc114e3..22f7d0d09 100644 --- a/Engine/lib/assimp/code/AssetLib/AC/ACLoader.h +++ b/Engine/lib/assimp/code/AssetLib/AC/ACLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -125,7 +125,6 @@ public: type(World), name(), children(), - texture(), texRepeat(1.f, 1.f), texOffset(0.0f, 0.0f), rotation(), @@ -151,7 +150,8 @@ public: std::vector children; // texture to be assigned to all surfaces of the object - std::string texture; + // the .acc format supports up to 4 textures + std::vector textures; // texture repat factors (scaling for all coordinates) aiVector2D texRepeat, texOffset; @@ -216,7 +216,7 @@ private: * load subobjects, the method returns after a 'kids 0' was * encountered. * @objects List of output objects*/ - void LoadObjectSection(std::vector &objects); + bool LoadObjectSection(std::vector &objects); // ------------------------------------------------------------------- /** Convert all objects into meshes and nodes. @@ -242,7 +242,7 @@ private: private: // points to the next data line - const char *buffer; + aiBuffer mBuffer; // Configuration option: if enabled, up to two meshes // are generated per material: those faces who have diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.cpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.cpp index 4103dcdf4..7861c592e 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,7 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -const aiImporterDesc AMFImporter::Description = { +static constexpr aiImporterDesc Description = { "Additive manufacturing file format(AMF) Importer", "smalcom", "", @@ -83,11 +83,7 @@ void AMFImporter::Clear() { AMFImporter::AMFImporter() AI_NO_EXCEPT : mNodeElement_Cur(nullptr), - mXmlParser(nullptr), - mUnit(), - mVersion(), - mMaterial_Converted(), - mTexture_Converted() { + mXmlParser(nullptr) { // empty } @@ -182,28 +178,6 @@ bool AMFImporter::XML_SearchNode(const std::string &nodeName) { return nullptr != mXmlParser->findNode(nodeName); } -void AMFImporter::ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString) { - size_t instr_len; - - pOutString.clear(); - instr_len = strlen(pInStr); - if (!instr_len) return; - - pOutString.reserve(instr_len * 3 / 2); - // check and correct floats in format ".x". Must be "x.y". - if (pInStr[0] == '.') pOutString.push_back('0'); - - pOutString.push_back(pInStr[0]); - for (size_t ci = 1; ci < instr_len; ci++) { - if ((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t'))) { - pOutString.push_back('0'); - pOutString.push_back('.'); - } else { - pOutString.push_back(pInStr[ci]); - } - } -} - static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) { return (isalnum((unsigned char)pChar) || (pChar == '+') || (pChar == '/')); } @@ -217,7 +191,10 @@ void AMFImporter::ParseHelper_Decode_Base64(const std::string &pInputBase64, std uint8_t arr4[4], arr3[3]; // check input data - if (pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + if (pInputBase64.size() % 4) { + throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + } + // prepare output place pOutputData.clear(); pOutputData.reserve(pInputBase64.size() / 4 * 3); @@ -261,7 +238,7 @@ void AMFImporter::ParseFile(const std::string &pFile, IOSystem *pIOHandler) { std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open AMF file ", pFile, "."); } @@ -407,17 +384,17 @@ void AMFImporter::ParseNode_Instance(XmlNode &node) { for (auto ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "deltax") { - XmlParser::getValueAsFloat(currentNode, als.Delta.x); + XmlParser::getValueAsReal(currentNode, als.Delta.x); } else if (currentName == "deltay") { - XmlParser::getValueAsFloat(currentNode, als.Delta.y); + XmlParser::getValueAsReal(currentNode, als.Delta.y); } else if (currentName == "deltaz") { - XmlParser::getValueAsFloat(currentNode, als.Delta.z); + XmlParser::getValueAsReal(currentNode, als.Delta.z); } else if (currentName == "rx") { - XmlParser::getValueAsFloat(currentNode, als.Delta.x); + XmlParser::getValueAsReal(currentNode, als.Delta.x); } else if (currentName == "ry") { - XmlParser::getValueAsFloat(currentNode, als.Delta.y); + XmlParser::getValueAsReal(currentNode, als.Delta.y); } else if (currentName == "rz") { - XmlParser::getValueAsFloat(currentNode, als.Delta.z); + XmlParser::getValueAsReal(currentNode, als.Delta.z); } } ParseHelper_Node_Exit(); diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.hpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.hpp index 601eae4e4..97e0a7118 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.hpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter.hpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -98,8 +98,12 @@ namespace Assimp { /// old - and children , , , , , /// class AMFImporter : public BaseImporter { -private: - struct SPP_Material; // forward declaration + using AMFMetaDataArray = std::vector; + using MeshArray = std::vector; + using NodeArray = std::vector; + +public: + struct SPP_Material; /// Data type for post-processing step. More suitable container for part of material's composition. struct SPP_Composite { @@ -107,22 +111,6 @@ private: std::string Formula; ///< Formula for calculating ratio of \ref Material. }; - /// \struct SPP_Material - /// Data type for post-processing step. More suitable container for material. - struct SPP_Material { - std::string ID; ///< Material ID. - std::list Metadata; ///< Metadata of material. - AMFColor *Color; ///< Color of material. - std::list Composition; ///< List of child materials if current material is composition of few another. - - /// Return color calculated for specified coordinate. - /// \param [in] pX - "x" coordinate. - /// \param [in] pY - "y" coordinate. - /// \param [in] pZ - "z" coordinate. - /// \return calculated color. - aiColor4D GetColor(const float pX, const float pY, const float pZ) const; - }; - /// Data type for post-processing step. More suitable container for texture. struct SPP_Texture { std::string ID; @@ -139,10 +127,51 @@ private: const AMFTexMap *TexMap; ///< Face texture mapping data. Equal to nullptr if texture mapping is not set for the face. }; - using AMFMetaDataArray = std::vector; - using MeshArray = std::vector; - using NodeArray = std::vector; + /// Data type for post-processing step. More suitable container for material. + struct SPP_Material { + std::string ID; ///< Material ID. + std::list Metadata; ///< Metadata of material. + AMFColor *Color; ///< Color of material. + std::list Composition; ///< List of child materials if current material is composition of few another. + /// Return color calculated for specified coordinate. + /// \param [in] pX - "x" coordinate. + /// \param [in] pY - "y" coordinate. + /// \param [in] pZ - "z" coordinate. + /// \return calculated color. + aiColor4D GetColor(const float pX, const float pY, const float pZ) const; + }; + + /// Default constructor. + AMFImporter() AI_NO_EXCEPT; + + /// Default destructor. + ~AMFImporter() override; + + /// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph. + /// Also exception can be thrown if trouble will found. + /// \param [in] pFile - name of file to be parsed. + /// \param [in] pIOHandler - pointer to IO helper object. + void ParseFile(const std::string &pFile, IOSystem *pIOHandler); + void ParseHelper_Node_Enter(AMFNodeElementBase *child); + void ParseHelper_Node_Exit(); + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + const aiImporterDesc *GetInfo() const override; + bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const; + bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const; + bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const; + AI_WONT_RETURN void Throw_CloseNotFound(const std::string &nodeName) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Throw_ID_NotFound(const std::string &pID) const AI_WONT_RETURN_SUFFIX; + void XML_CheckNode_MustHaveChildren(pugi::xml_node &node); + bool XML_SearchNode(const std::string &nodeName); + AMFImporter(const AMFImporter &pScene) = delete; + AMFImporter &operator=(const AMFImporter &pScene) = delete; + +private: /// Clear all temporary data. void Clear(); @@ -262,40 +291,9 @@ private: /// \param [in] pUseOldName - if true then use old name of node(and children) - , instead of new name - . void ParseNode_TexMap(XmlNode &node, const bool pUseOldName = false); -public: - /// Default constructor. - AMFImporter() AI_NO_EXCEPT; - /// Default destructor. - ~AMFImporter() override; - - /// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph. - /// Also exception can be thrown if trouble will found. - /// \param [in] pFile - name of file to be parsed. - /// \param [in] pIOHandler - pointer to IO helper object. - void ParseFile(const std::string &pFile, IOSystem *pIOHandler); - void ParseHelper_Node_Enter(AMFNodeElementBase *child); - void ParseHelper_Node_Exit(); - bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const override; - void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; - const aiImporterDesc *GetInfo() const override; - bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const; - bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const; - bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const; - void Throw_CloseNotFound(const std::string &nodeName); - void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName); - void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName); - void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription); - void Throw_ID_NotFound(const std::string &pID) const; - void XML_CheckNode_MustHaveChildren(pugi::xml_node &node); - bool XML_SearchNode(const std::string &nodeName); - void ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString); - AMFImporter(const AMFImporter &pScene) = delete; - AMFImporter &operator=(const AMFImporter &pScene) = delete; private: - static const aiImporterDesc Description; - AMFNodeElementBase *mNodeElement_Cur; ///< Current element. std::list mNodeElement_List; ///< All elements of scene graph. XmlParser *mXmlParser; diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp index 341999f56..b1d87eb2e 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Geometry.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -167,11 +167,11 @@ void AMFImporter::ParseNode_Coordinates(XmlNode &node) { AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience const std::string ¤tName = ai_tolower(currentNode.name()); if (currentName == "x") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.x); + XmlParser::getValueAsReal(currentNode, als.Coordinate.x); } else if (currentName == "y") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.y); + XmlParser::getValueAsReal(currentNode, als.Coordinate.y); } else if (currentName == "z") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.z); + XmlParser::getValueAsReal(currentNode, als.Coordinate.z); } } ParseHelper_Node_Exit(); diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp index 676e74856..74fe2a1b6 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Material.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -263,26 +263,25 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { const std::string &name = currentNode.name(); if (name == "utex1") { read_flag[0] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[0].x); } else if (name == "utex2") { read_flag[1] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[1].x); } else if (name == "utex3") { read_flag[2] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[2].x); } else if (name == "vtex1") { read_flag[3] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[0].y); } else if (name == "vtex2") { read_flag[4] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[1].y); } else if (name == "vtex3") { read_flag[5] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[2].y); } } ParseHelper_Node_Exit(); - } else { for (pugi::xml_attribute &attr : node.attributes()) { const std::string name = attr.name(); diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp index c827533a6..2b4f6717d 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Node.hpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,7 +56,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -/// \class CAMFImporter_NodeElement /// Base class for elements of nodes. class AMFNodeElementBase { public: @@ -88,9 +87,7 @@ public: std::list Child; ///< Child elements. public: /// Destructor, virtual.. - virtual ~AMFNodeElementBase() { - // empty - } + virtual ~AMFNodeElementBase() = default; /// Disabled copy constructor and co. AMFNodeElementBase(const AMFNodeElementBase &pNodeElement) = delete; @@ -103,12 +100,11 @@ protected: /// \param [in] pType - element type. /// \param [in] pParent - parent element. AMFNodeElementBase(const EType pType, AMFNodeElementBase *pParent) : - Type(pType), ID(), Parent(pParent), Child() { + Type(pType), Parent(pParent) { // empty } }; // class IAMFImporter_NodeElement -/// \struct CAMFImporter_NodeElement_Constellation /// A collection of objects or constellations with specific relative locations. struct AMFConstellation : public AMFNodeElementBase { /// Constructor. @@ -118,7 +114,6 @@ struct AMFConstellation : public AMFNodeElementBase { }; // struct CAMFImporter_NodeElement_Constellation -/// \struct CAMFImporter_NodeElement_Instance /// Part of constellation. struct AMFInstance : public AMFNodeElementBase { @@ -137,7 +132,6 @@ struct AMFInstance : public AMFNodeElementBase { AMFNodeElementBase(ENET_Instance, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Metadata /// Structure that define metadata node. struct AMFMetadata : public AMFNodeElementBase { @@ -150,7 +144,6 @@ struct AMFMetadata : public AMFNodeElementBase { AMFNodeElementBase(ENET_Metadata, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Root /// Structure that define root node. struct AMFRoot : public AMFNodeElementBase { @@ -163,7 +156,6 @@ struct AMFRoot : public AMFNodeElementBase { AMFNodeElementBase(ENET_Root, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Color /// Structure that define object node. struct AMFColor : public AMFNodeElementBase { bool Composed; ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color. @@ -174,12 +166,11 @@ struct AMFColor : public AMFNodeElementBase { /// @brief Constructor. /// @param [in] pParent - pointer to parent node. AMFColor(AMFNodeElementBase *pParent) : - AMFNodeElementBase(ENET_Color, pParent), Composed(false), Color(), Profile() { + AMFNodeElementBase(ENET_Color, pParent), Composed(false), Color() { // empty } }; -/// \struct CAMFImporter_NodeElement_Material /// Structure that define material node. struct AMFMaterial : public AMFNodeElementBase { @@ -189,7 +180,6 @@ struct AMFMaterial : public AMFNodeElementBase { AMFNodeElementBase(ENET_Material, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Object /// Structure that define object node. struct AMFObject : public AMFNodeElementBase { @@ -208,7 +198,6 @@ struct AMFMesh : public AMFNodeElementBase { AMFNodeElementBase(ENET_Mesh, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Vertex /// Structure that define vertex node. struct AMFVertex : public AMFNodeElementBase { /// Constructor. @@ -217,7 +206,6 @@ struct AMFVertex : public AMFNodeElementBase { AMFNodeElementBase(ENET_Vertex, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Edge /// Structure that define edge node. struct AMFEdge : public AMFNodeElementBase { /// Constructor. @@ -226,7 +214,6 @@ struct AMFEdge : public AMFNodeElementBase { AMFNodeElementBase(ENET_Edge, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Vertices /// Structure that define vertices node. struct AMFVertices : public AMFNodeElementBase { /// Constructor. @@ -235,7 +222,6 @@ struct AMFVertices : public AMFNodeElementBase { AMFNodeElementBase(ENET_Vertices, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Volume /// Structure that define volume node. struct AMFVolume : public AMFNodeElementBase { std::string MaterialID; ///< Which material to use. @@ -247,7 +233,6 @@ struct AMFVolume : public AMFNodeElementBase { AMFNodeElementBase(ENET_Volume, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Coordinates /// Structure that define coordinates node. struct AMFCoordinates : public AMFNodeElementBase { aiVector3D Coordinate; ///< Coordinate. @@ -258,7 +243,6 @@ struct AMFCoordinates : public AMFNodeElementBase { AMFNodeElementBase(ENET_Coordinates, pParent) {} }; -/// \struct CAMFImporter_NodeElement_TexMap /// Structure that define texture coordinates node. struct AMFTexMap : public AMFNodeElementBase { aiVector3D TextureCoordinate[3]; ///< Texture coordinates. @@ -270,12 +254,11 @@ struct AMFTexMap : public AMFNodeElementBase { /// Constructor. /// \param [in] pParent - pointer to parent node. AMFTexMap(AMFNodeElementBase *pParent) : - AMFNodeElementBase(ENET_TexMap, pParent), TextureCoordinate{}, TextureID_R(), TextureID_G(), TextureID_B(), TextureID_A() { + AMFNodeElementBase(ENET_TexMap, pParent), TextureCoordinate{} { // empty } }; -/// \struct CAMFImporter_NodeElement_Triangle /// Structure that define triangle node. struct AMFTriangle : public AMFNodeElementBase { size_t V[3]; ///< Triangle vertices. diff --git a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp index a65f9260e..e491c07b7 100644 --- a/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp +++ b/Engine/lib/assimp/code/AssetLib/AMF/AMFImporter_Postprocess.cpp @@ -1,9 +1,9 @@ -/* +/* --------------------------------------------------------------------------- Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -224,7 +224,8 @@ size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string & } // Create format hint. - strcpy(converted_texture.FormatHint, "rgba0000"); // copy initial string. + constexpr char templateColor[] = "rgba0000"; + memcpy(converted_texture.FormatHint, templateColor, 8); if (!r.empty()) converted_texture.FormatHint[4] = '8'; if (!g.empty()) converted_texture.FormatHint[5] = '8'; if (!b.empty()) converted_texture.FormatHint[6] = '8'; @@ -815,6 +816,7 @@ nl_clean_loop: for (; next_it != nodeArray.end(); ++next_it) { if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) { // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop. + // FIXME: this leaks memory on test models test8.amf and test9.amf nodeArray.erase(nl_it); goto nl_clean_loop; @@ -866,7 +868,7 @@ nl_clean_loop: pScene->mTextures[idx]->mHeight = static_cast(tex_convd.Height); pScene->mTextures[idx]->pcData = (aiTexel *)tex_convd.Data; // texture format description. - strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint); + strncpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint, HINTMAXTEXTURELEN); idx++; } // for(const SPP_Texture& tex_convd: mTexture_Converted) diff --git a/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.cpp b/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.cpp index f78ff99fb..c5f2eba32 100644 --- a/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -44,7 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER - #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER // internal headers @@ -64,10 +63,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // utilities #include -using namespace Assimp; +namespace Assimp { using namespace Assimp::ASE; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "ASE Importer", "", "", @@ -87,10 +86,6 @@ ASEImporter::ASEImporter() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ASEImporter::~ASEImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool ASEImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -122,14 +117,14 @@ void ASEImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open ASE file ", pFile, "."); } // Allocate storage and copy the contents of the file to a memory buffer std::vector mBuffer2; TextFileToBuffer(file.get(), mBuffer2); - + const size_t fileSize = mBuffer2.size(); this->mBuffer = &mBuffer2[0]; this->pcScene = pScene; @@ -151,7 +146,7 @@ void ASEImporter::InternReadFile(const std::string &pFile, }; // Construct an ASE parser and parse the file - ASE::Parser parser(mBuffer, defaultFormat); + ASE::Parser parser(mBuffer, fileSize, defaultFormat); mParser = &parser; mParser->Parse(); @@ -326,21 +321,6 @@ void ASEImporter::BuildAnimations(const std::vector &nodes) { aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); nd->mNodeName.Set(me->mName + ".Target"); - // If there is no input position channel we will need - // to supply the default position from the node's - // local transformation matrix. - /*TargetAnimationHelper helper; - if (me->mAnim.akeyPositions.empty()) - { - aiMatrix4x4& mat = (*i)->mTransform; - helper.SetFixedMainAnimationChannel(aiVector3D( - mat.a4, mat.b4, mat.c4)); - } - else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions); - helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions); - - helper.Process(&me->mTargetAnim.akeyPositions);*/ - // Allocate the key array and fill it nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size(); nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; @@ -466,10 +446,9 @@ void ASEImporter::BuildLights() { } // ------------------------------------------------------------------------------------------------ -void ASEImporter::AddNodes(const std::vector &nodes, - aiNode *pcParent, const char *szName) { +void ASEImporter::AddNodes(const std::vector &nodes, aiNode *pcParent, const std::string &name) { aiMatrix4x4 m; - AddNodes(nodes, pcParent, szName, m); + AddNodes(nodes, pcParent, name, m); } // ------------------------------------------------------------------------------------------------ @@ -526,10 +505,9 @@ void ASEImporter::AddMeshes(const ASE::BaseNode *snode, aiNode *node) { // ------------------------------------------------------------------------------------------------ // Add child nodes to a given parent node -void ASEImporter::AddNodes(const std::vector &nodes, - aiNode *pcParent, const char *szName, +void ASEImporter::AddNodes(const std::vector &nodes, aiNode *pcParent, const std::string &name, const aiMatrix4x4 &mat) { - const size_t len = szName ? ::strlen(szName) : 0; + const size_t len = name.size(); ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); // Receives child nodes for the pcParent node @@ -539,16 +517,18 @@ void ASEImporter::AddNodes(const std::vector &nodes, // which has *us* as parent. for (std::vector::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { const BaseNode *snode = *it; - if (szName) { - if (len != snode->mParent.length() || ::strcmp(szName, snode->mParent.c_str())) + if (!name.empty()) { + if (len != snode->mParent.length() || name != snode->mParent.c_str()) { continue; - } else if (snode->mParent.length()) + } + } else if (snode->mParent.length()) { continue; + } (*it)->mProcessed = true; // Allocate a new node and add it to the output data structure - apcNodes.push_back(new aiNode()); + apcNodes.push_back(new aiNode); aiNode *node = apcNodes.back(); node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); @@ -561,7 +541,7 @@ void ASEImporter::AddNodes(const std::vector &nodes, // Add sub nodes - prevent stack overflow due to recursive parenting if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName) { - AddNodes(nodes, node, node->mName.data, snode->mTransform); + AddNodes(nodes, node, node->mName.C_Str(), snode->mTransform); } // Further processing depends on the type of the node @@ -639,7 +619,8 @@ void ASEImporter::BuildNodes(std::vector &nodes) { } // add all nodes - AddNodes(nodes, ch, nullptr); + static const std::string none = ""; + AddNodes(nodes, ch, none); // now iterate through al nodes and find those that have not yet // been added to the nodegraph (= their parent could not be recognized) @@ -924,7 +905,7 @@ void ASEImporter::ConvertMeshes(ASE::Mesh &mesh, std::vector &avOutMes ASSIMP_LOG_WARN("Material index is out of range"); } - // If the material the mesh is assigned to is consisting of submeshes, split it + // If the material the mesh is assigned to consists of submeshes, split it if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) { std::vector vSubMaterials = mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials; @@ -1282,6 +1263,8 @@ bool ASEImporter::GenerateNormals(ASE::Mesh &mesh) { return false; } +} + #endif // ASSIMP_BUILD_NO_3DS_IMPORTER #endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.h b/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.h index cd9123556..99d5119ed 100644 --- a/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.h +++ b/Engine/lib/assimp/code/AssetLib/ASE/ASELoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,7 +62,7 @@ namespace Assimp { class ASEImporter : public BaseImporter { public: ASEImporter(); - ~ASEImporter() override; + ~ASEImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -153,13 +153,13 @@ private: * \param matrix Current transform */ void AddNodes(const std::vector& nodes, - aiNode* pcParent,const char* szName); + aiNode* pcParent, const std::string &name); void AddNodes(const std::vector& nodes, - aiNode* pcParent,const char* szName, + aiNode* pcParent, const std::string &name, const aiMatrix4x4& matrix); - void AddMeshes(const ASE::BaseNode* snode,aiNode* node); + void AddMeshes(const ASE::BaseNode* snode, aiNode* node); // ------------------------------------------------------------------- /** Generate a default material and add it to the parser's list @@ -188,5 +188,4 @@ protected: } // end of namespace Assimp - #endif // AI_3DSIMPORTER_H_INC diff --git a/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.cpp b/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.cpp index 96346bdcb..c9bbe3ca6 100644 --- a/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,7 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { using namespace Assimp::ASE; // ------------------------------------------------------------------------------------------------ @@ -66,23 +66,24 @@ using namespace Assimp::ASE; // Handle a "top-level" section in the file. EOF is no error in this case. #define AI_ASE_HANDLE_TOP_LEVEL_SECTION() \ - else if ('{' == *filePtr) iDepth++; \ - else if ('}' == *filePtr) { \ + else if ('{' == *mFilePtr) \ + ++iDepth; \ + else if ('}' == *mFilePtr) { \ if (0 == --iDepth) { \ - ++filePtr; \ + ++mFilePtr; \ SkipToNextToken(); \ return; \ } \ } \ - if ('\0' == *filePtr) { \ + if ('\0' == *mFilePtr) { \ return; \ } \ - if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + if (IsLineEnd(*mFilePtr) && !bLastWasEndLine) {\ ++iLineNumber; \ bLastWasEndLine = true; \ } else \ bLastWasEndLine = false; \ - ++filePtr; + ++mFilePtr; // ------------------------------------------------------------------------------------------------ // Handle a nested section in the file. EOF is an error in this case @@ -90,30 +91,32 @@ using namespace Assimp::ASE; // @param msg Full name of the section (including the asterisk) #define AI_ASE_HANDLE_SECTION(level, msg) \ - if ('{' == *filePtr) \ + if ('{' == *mFilePtr) \ iDepth++; \ - else if ('}' == *filePtr) { \ + else if ('}' == *mFilePtr) { \ if (0 == --iDepth) { \ - ++filePtr; \ + ++mFilePtr; \ SkipToNextToken(); \ return; \ } \ - } else if ('\0' == *filePtr) { \ + } else if ('\0' == *mFilePtr) { \ LogError("Encountered unexpected EOL while parsing a " msg \ " chunk (Level " level ")"); \ } \ - if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + if (IsLineEnd(*mFilePtr) && !bLastWasEndLine) { \ ++iLineNumber; \ bLastWasEndLine = true; \ } else \ bLastWasEndLine = false; \ - ++filePtr; + ++mFilePtr; // ------------------------------------------------------------------------------------------------ -Parser::Parser(const char *szFile, unsigned int fileFormatDefault) { - ai_assert(nullptr != szFile); +Parser::Parser(const char *file, size_t fileLen, unsigned int fileFormatDefault) : + mFilePtr(nullptr), mEnd (nullptr) { + ai_assert(file != nullptr); - filePtr = szFile; + mFilePtr = file; + mEnd = mFilePtr + fileLen; iFileFormat = fileFormatDefault; // make sure that the color values are invalid @@ -177,7 +180,11 @@ AI_WONT_RETURN void Parser::LogError(const char *szWarn) { // ------------------------------------------------------------------------------------------------ bool Parser::SkipToNextToken() { while (true) { - char me = *filePtr; + char me = *mFilePtr; + + if (mFilePtr == mEnd) { + return false; + } // increase the line number counter if necessary if (IsLineEnd(me) && !bLastWasEndLine) { @@ -185,10 +192,14 @@ bool Parser::SkipToNextToken() { bLastWasEndLine = true; } else bLastWasEndLine = false; - if ('*' == me || '}' == me || '{' == me) return true; - if ('\0' == me) return false; + if ('*' == me || '}' == me || '{' == me) { + return true; + } + if ('\0' == me) { + return false; + } - ++filePtr; + ++mFilePtr; } } @@ -197,22 +208,22 @@ bool Parser::SkipSection() { // must handle subsections ... int iCnt = 0; while (true) { - if ('}' == *filePtr) { + if ('}' == *mFilePtr) { --iCnt; if (0 == iCnt) { // go to the next valid token ... - ++filePtr; + ++mFilePtr; SkipToNextToken(); return true; } - } else if ('{' == *filePtr) { + } else if ('{' == *mFilePtr) { ++iCnt; - } else if ('\0' == *filePtr) { + } else if ('\0' == *mFilePtr) { LogWarning("Unable to parse block: Unexpected EOF, closing bracket \'}\' was expected [#1]"); return false; - } else if (IsLineEnd(*filePtr)) + } else if (IsLineEnd(*mFilePtr)) ++iLineNumber; - ++filePtr; + ++mFilePtr; } } @@ -220,11 +231,11 @@ bool Parser::SkipSection() { void Parser::Parse() { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Version should be 200. Validate this ... - if (TokenMatch(filePtr, "3DSMAX_ASCIIEXPORT", 18)) { + if (TokenMatch(mFilePtr, "3DSMAX_ASCIIEXPORT", 18)) { unsigned int fmt; ParseLV4MeshLong(fmt); @@ -245,23 +256,23 @@ void Parser::Parse() { continue; } // main scene information - if (TokenMatch(filePtr, "SCENE", 5)) { + if (TokenMatch(mFilePtr, "SCENE", 5)) { ParseLV1SceneBlock(); continue; } // "group" - no implementation yet, in facte // we're just ignoring them for the moment - if (TokenMatch(filePtr, "GROUP", 5)) { + if (TokenMatch(mFilePtr, "GROUP", 5)) { Parse(); continue; } // material list - if (TokenMatch(filePtr, "MATERIAL_LIST", 13)) { + if (TokenMatch(mFilePtr, "MATERIAL_LIST", 13)) { ParseLV1MaterialListBlock(); continue; } // geometric object (mesh) - if (TokenMatch(filePtr, "GEOMOBJECT", 10)) + if (TokenMatch(mFilePtr, "GEOMOBJECT", 10)) { m_vMeshes.emplace_back("UNNAMED"); @@ -269,7 +280,7 @@ void Parser::Parse() { continue; } // helper object = dummy in the hierarchy - if (TokenMatch(filePtr, "HELPEROBJECT", 12)) + if (TokenMatch(mFilePtr, "HELPEROBJECT", 12)) { m_vDummies.emplace_back(); @@ -277,7 +288,7 @@ void Parser::Parse() { continue; } // light object - if (TokenMatch(filePtr, "LIGHTOBJECT", 11)) + if (TokenMatch(mFilePtr, "LIGHTOBJECT", 11)) { m_vLights.emplace_back("UNNAMED"); @@ -285,26 +296,25 @@ void Parser::Parse() { continue; } // camera object - if (TokenMatch(filePtr, "CAMERAOBJECT", 12)) { + if (TokenMatch(mFilePtr, "CAMERAOBJECT", 12)) { m_vCameras.emplace_back("UNNAMED"); ParseLV1ObjectBlock(m_vCameras.back()); continue; } // comment - print it on the console - if (TokenMatch(filePtr, "COMMENT", 7)) { + if (TokenMatch(mFilePtr, "COMMENT", 7)) { std::string out = ""; ParseString(out, "*COMMENT"); LogInfo(("Comment: " + out).c_str()); continue; } // ASC bone weights - if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(filePtr, "MESH_SOFTSKINVERTS", 18)) { + if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(mFilePtr, "MESH_SOFTSKINVERTS", 18)) { ParseLV1SoftSkinBlock(); } } AI_ASE_HANDLE_TOP_LEVEL_SECTION(); } - return; } // ------------------------------------------------------------------------------------------------ @@ -331,24 +341,25 @@ void Parser::ParseLV1SoftSkinBlock() { */ // ************************************************************** while (true) { - if (*filePtr == '}') { - ++filePtr; + if (*mFilePtr == '}') { + ++mFilePtr; return; - } else if (*filePtr == '\0') + } else if (*mFilePtr == '\0') return; - else if (*filePtr == '{') - ++filePtr; + else if (*mFilePtr == '{') + ++mFilePtr; else // if (!IsSpace(*filePtr) && !IsLineEnd(*filePtr)) { ASE::Mesh *curMesh = nullptr; unsigned int numVerts = 0; - const char *sz = filePtr; - while (!IsSpaceOrNewLine(*filePtr)) - ++filePtr; + const char *sz = mFilePtr; + while (!IsSpaceOrNewLine(*mFilePtr)) { + ++mFilePtr; + } - const unsigned int diff = (unsigned int)(filePtr - sz); + const unsigned int diff = (unsigned int)(mFilePtr - sz); if (diff) { std::string name = std::string(sz, diff); for (std::vector::iterator it = m_vMeshes.begin(); @@ -364,24 +375,24 @@ void Parser::ParseLV1SoftSkinBlock() { // Skip the mesh data - until we find a new mesh // or the end of the *MESH_SOFTSKINVERTS section while (true) { - SkipSpacesAndLineEnd(&filePtr); - if (*filePtr == '}') { - ++filePtr; + SkipSpacesAndLineEnd(&mFilePtr, mEnd); + if (*mFilePtr == '}') { + ++mFilePtr; return; - } else if (!IsNumeric(*filePtr)) + } else if (!IsNumeric(*mFilePtr)) break; - SkipLine(&filePtr); + SkipLine(&mFilePtr, mEnd); } } else { - SkipSpacesAndLineEnd(&filePtr); + SkipSpacesAndLineEnd(&mFilePtr, mEnd); ParseLV4MeshLong(numVerts); // Reserve enough storage curMesh->mBoneVertices.reserve(numVerts); for (unsigned int i = 0; i < numVerts; ++i) { - SkipSpacesAndLineEnd(&filePtr); + SkipSpacesAndLineEnd(&mFilePtr, mEnd); unsigned int numWeights; ParseLV4MeshLong(numWeights); @@ -411,7 +422,7 @@ void Parser::ParseLV1SoftSkinBlock() { me.first = static_cast(curMesh->mBones.size()); curMesh->mBones.emplace_back(bone); } - ParseLV4MeshFloat(me.second); + ParseLV4MeshReal(me.second); // Add the new bone weight to list vert.mBoneWeights.push_back(me); @@ -420,10 +431,10 @@ void Parser::ParseLV1SoftSkinBlock() { } } } - if (*filePtr == '\0') + if (*mFilePtr == '\0') return; - ++filePtr; - SkipSpacesAndLineEnd(&filePtr); + ++mFilePtr; + SkipSpacesAndLineEnd(&mFilePtr, mEnd); } } @@ -431,35 +442,35 @@ void Parser::ParseLV1SoftSkinBlock() { void Parser::ParseLV1SceneBlock() { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "SCENE_BACKGROUND_STATIC", 23)) + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "SCENE_BACKGROUND_STATIC", 23)) { // parse a color triple and assume it is really the bg color ParseLV4MeshFloatTriple(&m_clrBackground.r); continue; } - if (TokenMatch(filePtr, "SCENE_AMBIENT_STATIC", 20)) + if (TokenMatch(mFilePtr, "SCENE_AMBIENT_STATIC", 20)) { // parse a color triple and assume it is really the bg color ParseLV4MeshFloatTriple(&m_clrAmbient.r); continue; } - if (TokenMatch(filePtr, "SCENE_FIRSTFRAME", 16)) { + if (TokenMatch(mFilePtr, "SCENE_FIRSTFRAME", 16)) { ParseLV4MeshLong(iFirstFrame); continue; } - if (TokenMatch(filePtr, "SCENE_LASTFRAME", 15)) { + if (TokenMatch(mFilePtr, "SCENE_LASTFRAME", 15)) { ParseLV4MeshLong(iLastFrame); continue; } - if (TokenMatch(filePtr, "SCENE_FRAMESPEED", 16)) { + if (TokenMatch(mFilePtr, "SCENE_FRAMESPEED", 16)) { ParseLV4MeshLong(iFrameSpeed); continue; } - if (TokenMatch(filePtr, "SCENE_TICKSPERFRAME", 19)) { + if (TokenMatch(mFilePtr, "SCENE_TICKSPERFRAME", 19)) { ParseLV4MeshLong(iTicksPerFrame); continue; } @@ -475,23 +486,34 @@ void Parser::ParseLV1MaterialListBlock() { unsigned int iMaterialCount = 0; unsigned int iOldMaterialCount = (unsigned int)m_vMaterials.size(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "MATERIAL_COUNT", 14)) { ParseLV4MeshLong(iMaterialCount); + if (UINT_MAX - iOldMaterialCount < iMaterialCount) { + LogWarning("Out of range: material index is too large"); + return; + } + // now allocate enough storage to hold all materials m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID")); continue; } - if (TokenMatch(filePtr, "MATERIAL", 8)) { + if (TokenMatch(mFilePtr, "MATERIAL", 8)) { + // ensure we have at least one material allocated + if (iMaterialCount == 0) { + LogWarning("*MATERIAL_COUNT unspecified or 0"); + iMaterialCount = 1; + m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID")); + } + unsigned int iIndex = 0; ParseLV4MeshLong(iIndex); if (iIndex >= iMaterialCount) { LogWarning("Out of range: material index is too large"); iIndex = iMaterialCount - 1; - return; } // get a reference to the material @@ -503,7 +525,7 @@ void Parser::ParseLV1MaterialListBlock() { if( iDepth == 1 ){ // CRUDE HACK: support missing brace after "Ascii Scene Exporter v2.51" LogWarning("Missing closing brace in material list"); - --filePtr; + --mFilePtr; return; } } @@ -517,37 +539,37 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { unsigned int iNumSubMaterials = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "MATERIAL_NAME", 13)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "MATERIAL_NAME", 13)) { if (!ParseString(mat.mName, "*MATERIAL_NAME")) SkipToNextToken(); continue; } // ambient material color - if (TokenMatch(filePtr, "MATERIAL_AMBIENT", 16)) { + if (TokenMatch(mFilePtr, "MATERIAL_AMBIENT", 16)) { ParseLV4MeshFloatTriple(&mat.mAmbient.r); continue; } // diffuse material color - if (TokenMatch(filePtr, "MATERIAL_DIFFUSE", 16)) { + if (TokenMatch(mFilePtr, "MATERIAL_DIFFUSE", 16)) { ParseLV4MeshFloatTriple(&mat.mDiffuse.r); continue; } // specular material color - if (TokenMatch(filePtr, "MATERIAL_SPECULAR", 17)) { + if (TokenMatch(mFilePtr, "MATERIAL_SPECULAR", 17)) { ParseLV4MeshFloatTriple(&mat.mSpecular.r); continue; } // material shading type - if (TokenMatch(filePtr, "MATERIAL_SHADING", 16)) { - if (TokenMatch(filePtr, "Blinn", 5)) { + if (TokenMatch(mFilePtr, "MATERIAL_SHADING", 16)) { + if (TokenMatch(mFilePtr, "Blinn", 5)) { mat.mShading = Discreet3DS::Blinn; - } else if (TokenMatch(filePtr, "Phong", 5)) { + } else if (TokenMatch(mFilePtr, "Phong", 5)) { mat.mShading = Discreet3DS::Phong; - } else if (TokenMatch(filePtr, "Flat", 4)) { + } else if (TokenMatch(mFilePtr, "Flat", 4)) { mat.mShading = Discreet3DS::Flat; - } else if (TokenMatch(filePtr, "Wire", 4)) { + } else if (TokenMatch(mFilePtr, "Wire", 4)) { mat.mShading = Discreet3DS::Wire; } else { // assume gouraud shading @@ -557,15 +579,15 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { continue; } // material transparency - if (TokenMatch(filePtr, "MATERIAL_TRANSPARENCY", 21)) { - ParseLV4MeshFloat(mat.mTransparency); + if (TokenMatch(mFilePtr, "MATERIAL_TRANSPARENCY", 21)) { + ParseLV4MeshReal(mat.mTransparency); mat.mTransparency = ai_real(1.0) - mat.mTransparency; continue; } // material self illumination - if (TokenMatch(filePtr, "MATERIAL_SELFILLUM", 18)) { + if (TokenMatch(mFilePtr, "MATERIAL_SELFILLUM", 18)) { ai_real f = 0.0; - ParseLV4MeshFloat(f); + ParseLV4MeshReal(f); mat.mEmissive.r = f; mat.mEmissive.g = f; @@ -573,71 +595,77 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { continue; } // material shininess - if (TokenMatch(filePtr, "MATERIAL_SHINE", 14)) { - ParseLV4MeshFloat(mat.mSpecularExponent); + if (TokenMatch(mFilePtr, "MATERIAL_SHINE", 14)) { + ParseLV4MeshReal(mat.mSpecularExponent); mat.mSpecularExponent *= 15; continue; } // two-sided material - if (TokenMatch(filePtr, "MATERIAL_TWOSIDED", 17)) { + if (TokenMatch(mFilePtr, "MATERIAL_TWOSIDED", 17)) { mat.mTwoSided = true; continue; } // material shininess strength - if (TokenMatch(filePtr, "MATERIAL_SHINESTRENGTH", 22)) { - ParseLV4MeshFloat(mat.mShininessStrength); + if (TokenMatch(mFilePtr, "MATERIAL_SHINESTRENGTH", 22)) { + ParseLV4MeshReal(mat.mShininessStrength); continue; } // diffuse color map - if (TokenMatch(filePtr, "MAP_DIFFUSE", 11)) { + if (TokenMatch(mFilePtr, "MAP_DIFFUSE", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexDiffuse); continue; } // ambient color map - if (TokenMatch(filePtr, "MAP_AMBIENT", 11)) { + if (TokenMatch(mFilePtr, "MAP_AMBIENT", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexAmbient); continue; } // specular color map - if (TokenMatch(filePtr, "MAP_SPECULAR", 12)) { + if (TokenMatch(mFilePtr, "MAP_SPECULAR", 12)) { // parse the texture block ParseLV3MapBlock(mat.sTexSpecular); continue; } // opacity map - if (TokenMatch(filePtr, "MAP_OPACITY", 11)) { + if (TokenMatch(mFilePtr, "MAP_OPACITY", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexOpacity); continue; } // emissive map - if (TokenMatch(filePtr, "MAP_SELFILLUM", 13)) { + if (TokenMatch(mFilePtr, "MAP_SELFILLUM", 13)) { // parse the texture block ParseLV3MapBlock(mat.sTexEmissive); continue; } // bump map - if (TokenMatch(filePtr, "MAP_BUMP", 8)) { + if (TokenMatch(mFilePtr, "MAP_BUMP", 8)) { // parse the texture block ParseLV3MapBlock(mat.sTexBump); } // specular/shininess map - if (TokenMatch(filePtr, "MAP_SHINESTRENGTH", 17)) { + if (TokenMatch(mFilePtr, "MAP_SHINESTRENGTH", 17)) { // parse the texture block ParseLV3MapBlock(mat.sTexShininess); continue; } // number of submaterials - if (TokenMatch(filePtr, "NUMSUBMTLS", 10)) { + if (TokenMatch(mFilePtr, "NUMSUBMTLS", 10)) { ParseLV4MeshLong(iNumSubMaterials); // allocate enough storage mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL")); } // submaterial chunks - if (TokenMatch(filePtr, "SUBMATERIAL", 11)) { + if (TokenMatch(mFilePtr, "SUBMATERIAL", 11)) { + // ensure we have at least one material allocated + if (iNumSubMaterials == 0) { + LogWarning("*NUMSUBMTLS unspecified or 0"); + iNumSubMaterials = 1; + mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL")); + } unsigned int iIndex = 0; ParseLV4MeshLong(iIndex); @@ -674,10 +702,10 @@ void Parser::ParseLV3MapBlock(Texture &map) { bool parsePath = true; std::string temp; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // type of map - if (TokenMatch(filePtr, "MAP_CLASS", 9)) { + if (TokenMatch(mFilePtr, "MAP_CLASS", 9)) { temp.clear(); if (!ParseString(temp, "*MAP_CLASS")) SkipToNextToken(); @@ -688,7 +716,7 @@ void Parser::ParseLV3MapBlock(Texture &map) { continue; } // path to the texture - if (parsePath && TokenMatch(filePtr, "BITMAP", 6)) { + if (parsePath && TokenMatch(mFilePtr, "BITMAP", 6)) { if (!ParseString(map.mMapName, "*BITMAP")) SkipToNextToken(); @@ -702,52 +730,51 @@ void Parser::ParseLV3MapBlock(Texture &map) { continue; } // offset on the u axis - if (TokenMatch(filePtr, "UVW_U_OFFSET", 12)) { - ParseLV4MeshFloat(map.mOffsetU); + if (TokenMatch(mFilePtr, "UVW_U_OFFSET", 12)) { + ParseLV4MeshReal(map.mOffsetU); continue; } // offset on the v axis - if (TokenMatch(filePtr, "UVW_V_OFFSET", 12)) { - ParseLV4MeshFloat(map.mOffsetV); + if (TokenMatch(mFilePtr, "UVW_V_OFFSET", 12)) { + ParseLV4MeshReal(map.mOffsetV); continue; } // tiling on the u axis - if (TokenMatch(filePtr, "UVW_U_TILING", 12)) { - ParseLV4MeshFloat(map.mScaleU); + if (TokenMatch(mFilePtr, "UVW_U_TILING", 12)) { + ParseLV4MeshReal(map.mScaleU); continue; } // tiling on the v axis - if (TokenMatch(filePtr, "UVW_V_TILING", 12)) { - ParseLV4MeshFloat(map.mScaleV); + if (TokenMatch(mFilePtr, "UVW_V_TILING", 12)) { + ParseLV4MeshReal(map.mScaleV); continue; } // rotation around the z-axis - if (TokenMatch(filePtr, "UVW_ANGLE", 9)) { - ParseLV4MeshFloat(map.mRotation); + if (TokenMatch(mFilePtr, "UVW_ANGLE", 9)) { + ParseLV4MeshReal(map.mRotation); continue; } // map blending factor - if (TokenMatch(filePtr, "MAP_AMOUNT", 10)) { - ParseLV4MeshFloat(map.mTextureBlend); + if (TokenMatch(mFilePtr, "MAP_AMOUNT", 10)) { + ParseLV4MeshReal(map.mTextureBlend); continue; } } AI_ASE_HANDLE_SECTION("3", "*MAP_XXXXXX"); } - return; } // ------------------------------------------------------------------------------------------------ bool Parser::ParseString(std::string &out, const char *szName) { char szBuffer[1024]; - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Unexpected EOL", szName); LogWarning(szBuffer); return false; } // there must be '"' - if ('\"' != *filePtr) { + if ('\"' != *mFilePtr) { ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected " "to be enclosed in double quotation marks", @@ -755,8 +782,8 @@ bool Parser::ParseString(std::string &out, const char *szName) { LogWarning(szBuffer); return false; } - ++filePtr; - const char *sz = filePtr; + ++mFilePtr; + const char *sz = mFilePtr; while (true) { if ('\"' == *sz) break; @@ -770,8 +797,8 @@ bool Parser::ParseString(std::string &out, const char *szName) { } sz++; } - out = std::string(filePtr, (uintptr_t)sz - (uintptr_t)filePtr); - filePtr = sz + 1; + out = std::string(mFilePtr, (uintptr_t)sz - (uintptr_t)mFilePtr); + mFilePtr = sz + 1; return true; } @@ -779,48 +806,48 @@ bool Parser::ParseString(std::string &out, const char *szName) { void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // first process common tokens such as node name and transform // name of the mesh/node - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { if (!ParseString(node.mName, "*NODE_NAME")) SkipToNextToken(); continue; } // name of the parent of the node - if (TokenMatch(filePtr, "NODE_PARENT", 11)) { + if (TokenMatch(mFilePtr, "NODE_PARENT", 11)) { if (!ParseString(node.mParent, "*NODE_PARENT")) SkipToNextToken(); continue; } // transformation matrix of the node - if (TokenMatch(filePtr, "NODE_TM", 7)) { + if (TokenMatch(mFilePtr, "NODE_TM", 7)) { ParseLV2NodeTransformBlock(node); continue; } // animation data of the node - if (TokenMatch(filePtr, "TM_ANIMATION", 12)) { + if (TokenMatch(mFilePtr, "TM_ANIMATION", 12)) { ParseLV2AnimationBlock(node); continue; } if (node.mType == BaseNode::Light) { // light settings - if (TokenMatch(filePtr, "LIGHT_SETTINGS", 14)) { + if (TokenMatch(mFilePtr, "LIGHT_SETTINGS", 14)) { ParseLV2LightSettingsBlock((ASE::Light &)node); continue; } // type of the light source - if (TokenMatch(filePtr, "LIGHT_TYPE", 10)) { - if (!ASSIMP_strincmp("omni", filePtr, 4)) { + if (TokenMatch(mFilePtr, "LIGHT_TYPE", 10)) { + if (!ASSIMP_strincmp("omni", mFilePtr, 4)) { ((ASE::Light &)node).mLightType = ASE::Light::OMNI; - } else if (!ASSIMP_strincmp("target", filePtr, 6)) { + } else if (!ASSIMP_strincmp("target", mFilePtr, 6)) { ((ASE::Light &)node).mLightType = ASE::Light::TARGET; - } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + } else if (!ASSIMP_strincmp("free", mFilePtr, 4)) { ((ASE::Light &)node).mLightType = ASE::Light::FREE; - } else if (!ASSIMP_strincmp("directional", filePtr, 11)) { + } else if (!ASSIMP_strincmp("directional", mFilePtr, 11)) { ((ASE::Light &)node).mLightType = ASE::Light::DIRECTIONAL; } else { LogWarning("Unknown kind of light source"); @@ -829,13 +856,13 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { } } else if (node.mType == BaseNode::Camera) { // Camera settings - if (TokenMatch(filePtr, "CAMERA_SETTINGS", 15)) { + if (TokenMatch(mFilePtr, "CAMERA_SETTINGS", 15)) { ParseLV2CameraSettingsBlock((ASE::Camera &)node); continue; - } else if (TokenMatch(filePtr, "CAMERA_TYPE", 11)) { - if (!ASSIMP_strincmp("target", filePtr, 6)) { + } else if (TokenMatch(mFilePtr, "CAMERA_TYPE", 11)) { + if (!ASSIMP_strincmp("target", mFilePtr, 6)) { ((ASE::Camera &)node).mCameraType = ASE::Camera::TARGET; - } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + } else if (!ASSIMP_strincmp("free", mFilePtr, 4)) { ((ASE::Camera &)node).mCameraType = ASE::Camera::FREE; } else { LogWarning("Unknown kind of camera"); @@ -845,13 +872,13 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { } else if (node.mType == BaseNode::Mesh) { // mesh data // FIX: Older files use MESH_SOFTSKIN - if (TokenMatch(filePtr, "MESH", 4) || - TokenMatch(filePtr, "MESH_SOFTSKIN", 13)) { + if (TokenMatch(mFilePtr, "MESH", 4) || + TokenMatch(mFilePtr, "MESH_SOFTSKIN", 13)) { ParseLV2MeshBlock((ASE::Mesh &)node); continue; } // mesh material index - if (TokenMatch(filePtr, "MATERIAL_REF", 12)) { + if (TokenMatch(mFilePtr, "MATERIAL_REF", 12)) { ParseLV4MeshLong(((ASE::Mesh &)node).iMaterialIndex); continue; } @@ -859,53 +886,51 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { } AI_ASE_HANDLE_TOP_LEVEL_SECTION(); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "CAMERA_NEAR", 11)) { - ParseLV4MeshFloat(camera.mNear); + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "CAMERA_NEAR", 11)) { + ParseLV4MeshReal(camera.mNear); continue; } - if (TokenMatch(filePtr, "CAMERA_FAR", 10)) { - ParseLV4MeshFloat(camera.mFar); + if (TokenMatch(mFilePtr, "CAMERA_FAR", 10)) { + ParseLV4MeshReal(camera.mFar); continue; } - if (TokenMatch(filePtr, "CAMERA_FOV", 10)) { - ParseLV4MeshFloat(camera.mFOV); + if (TokenMatch(mFilePtr, "CAMERA_FOV", 10)) { + ParseLV4MeshReal(camera.mFOV); continue; } } AI_ASE_HANDLE_SECTION("2", "CAMERA_SETTINGS"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV2LightSettingsBlock(ASE::Light &light) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "LIGHT_COLOR", 11)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "LIGHT_COLOR", 11)) { ParseLV4MeshFloatTriple(&light.mColor.r); continue; } - if (TokenMatch(filePtr, "LIGHT_INTENS", 12)) { - ParseLV4MeshFloat(light.mIntensity); + if (TokenMatch(mFilePtr, "LIGHT_INTENS", 12)) { + ParseLV4MeshReal(light.mIntensity); continue; } - if (TokenMatch(filePtr, "LIGHT_HOTSPOT", 13)) { - ParseLV4MeshFloat(light.mAngle); + if (TokenMatch(mFilePtr, "LIGHT_HOTSPOT", 13)) { + ParseLV4MeshReal(light.mAngle); continue; } - if (TokenMatch(filePtr, "LIGHT_FALLOFF", 13)) { - ParseLV4MeshFloat(light.mFalloff); + if (TokenMatch(mFilePtr, "LIGHT_FALLOFF", 13)) { + ParseLV4MeshReal(light.mFalloff); continue; } } @@ -919,9 +944,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { ASE::Animation *anim = &mesh.mAnim; while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { std::string temp; if (!ParseString(temp, "*NODE_NAME")) SkipToNextToken(); @@ -943,9 +968,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { } // position keyframes - if (TokenMatch(filePtr, "CONTROL_POS_TRACK", 17) || - TokenMatch(filePtr, "CONTROL_POS_BEZIER", 18) || - TokenMatch(filePtr, "CONTROL_POS_TCB", 15)) { + if (TokenMatch(mFilePtr, "CONTROL_POS_TRACK", 17) || + TokenMatch(mFilePtr, "CONTROL_POS_BEZIER", 18) || + TokenMatch(mFilePtr, "CONTROL_POS_TCB", 15)) { if (!anim) SkipSection(); else @@ -953,9 +978,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { continue; } // scaling keyframes - if (TokenMatch(filePtr, "CONTROL_SCALE_TRACK", 19) || - TokenMatch(filePtr, "CONTROL_SCALE_BEZIER", 20) || - TokenMatch(filePtr, "CONTROL_SCALE_TCB", 17)) { + if (TokenMatch(mFilePtr, "CONTROL_SCALE_TRACK", 19) || + TokenMatch(mFilePtr, "CONTROL_SCALE_BEZIER", 20) || + TokenMatch(mFilePtr, "CONTROL_SCALE_TCB", 17)) { if (!anim || anim == &mesh.mTargetAnim) { // Target animation channels may have no rotation channels ASSIMP_LOG_ERROR("ASE: Ignoring scaling channel in target animation"); @@ -965,9 +990,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { continue; } // rotation keyframes - if (TokenMatch(filePtr, "CONTROL_ROT_TRACK", 17) || - TokenMatch(filePtr, "CONTROL_ROT_BEZIER", 18) || - TokenMatch(filePtr, "CONTROL_ROT_TCB", 15)) { + if (TokenMatch(mFilePtr, "CONTROL_ROT_TRACK", 17) || + TokenMatch(mFilePtr, "CONTROL_ROT_BEZIER", 18) || + TokenMatch(mFilePtr, "CONTROL_ROT_TCB", 15)) { if (!anim || anim == &mesh.mTargetAnim) { // Target animation channels may have no rotation channels ASSIMP_LOG_ERROR("ASE: Ignoring rotation channel in target animation"); @@ -986,8 +1011,8 @@ void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -995,25 +1020,25 @@ void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_SCALE_SAMPLE", 20)) { + if (TokenMatch(mFilePtr, "CONTROL_SCALE_SAMPLE", 20)) { b = true; anim.mScalingType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_SCALE_KEY", 24)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_SCALE_KEY", 24)) { b = true; anim.mScalingType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_SCALE_KEY", 21)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_SCALE_KEY", 21)) { b = true; anim.mScalingType = ASE::Animation::TCB; } if (b) { anim.akeyScaling.emplace_back(); aiVectorKey &key = anim.akeyScaling.back(); - ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + ParseLV4MeshRealTriple(&key.mValue.x, iIndex); key.mTime = (double)iIndex; } } @@ -1025,8 +1050,8 @@ void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { AI_ASE_PARSER_INIT(); unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -1034,25 +1059,25 @@ void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_POS_SAMPLE", 18)) { + if (TokenMatch(mFilePtr, "CONTROL_POS_SAMPLE", 18)) { b = true; anim.mPositionType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_POS_KEY", 22)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_POS_KEY", 22)) { b = true; anim.mPositionType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_POS_KEY", 19)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_POS_KEY", 19)) { b = true; anim.mPositionType = ASE::Animation::TCB; } if (b) { anim.akeyPositions.emplace_back(); aiVectorKey &key = anim.akeyPositions.back(); - ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + ParseLV4MeshRealTriple(&key.mValue.x, iIndex); key.mTime = (double)iIndex; } } @@ -1064,8 +1089,8 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { AI_ASE_PARSER_INIT(); unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -1073,18 +1098,18 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_ROT_SAMPLE", 18)) { + if (TokenMatch(mFilePtr, "CONTROL_ROT_SAMPLE", 18)) { b = true; anim.mRotationType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_ROT_KEY", 22)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_ROT_KEY", 22)) { b = true; anim.mRotationType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_ROT_KEY", 19)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_ROT_KEY", 19)) { b = true; anim.mRotationType = ASE::Animation::TCB; } @@ -1093,8 +1118,8 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { aiQuatKey &key = anim.akeyRotations.back(); aiVector3D v; ai_real f; - ParseLV4MeshFloatTriple(&v.x, iIndex); - ParseLV4MeshFloat(f); + ParseLV4MeshRealTriple(&v.x, iIndex); + ParseLV4MeshReal(f); key.mTime = (double)iIndex; key.mValue = aiQuaternion(v, f); } @@ -1107,10 +1132,10 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { AI_ASE_PARSER_INIT(); int mode = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // name of the node - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { std::string temp; if (!ParseString(temp, "*NODE_NAME")) SkipToNextToken(); @@ -1137,28 +1162,28 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { if (mode) { // fourth row of the transformation matrix - and also the // only information here that is interesting for targets - if (TokenMatch(filePtr, "TM_ROW3", 7)) { - ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); + if (TokenMatch(mFilePtr, "TM_ROW3", 7)) { + ParseLV4MeshRealTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); continue; } if (mode == 1) { // first row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW0", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[0]); + if (TokenMatch(mFilePtr, "TM_ROW0", 7)) { + ParseLV4MeshRealTriple(mesh.mTransform[0]); continue; } // second row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW1", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[1]); + if (TokenMatch(mFilePtr, "TM_ROW1", 7)) { + ParseLV4MeshRealTriple(mesh.mTransform[1]); continue; } // third row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW2", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[2]); + if (TokenMatch(mFilePtr, "TM_ROW2", 7)) { + ParseLV4MeshRealTriple(mesh.mTransform[2]); continue; } // inherited position axes - if (TokenMatch(filePtr, "INHERIT_POS", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_POS", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1167,7 +1192,7 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { continue; } // inherited rotation axes - if (TokenMatch(filePtr, "INHERIT_ROT", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_ROT", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1176,7 +1201,7 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { continue; } // inherited scaling axes - if (TokenMatch(filePtr, "INHERIT_SCL", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_SCL", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1189,7 +1214,6 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { } AI_ASE_HANDLE_SECTION("2", "*NODE_TM"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { @@ -1202,75 +1226,75 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { unsigned int iNumCVertices = 0; unsigned int iNumCFaces = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of vertices in the mesh - if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + if (TokenMatch(mFilePtr, "MESH_NUMVERTEX", 14)) { ParseLV4MeshLong(iNumVertices); continue; } // Number of texture coordinates in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVERTEX", 15)) { ParseLV4MeshLong(iNumTVertices); continue; } // Number of vertex colors in the mesh - if (TokenMatch(filePtr, "MESH_NUMCVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMCVERTEX", 15)) { ParseLV4MeshLong(iNumCVertices); continue; } // Number of regular faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMFACES", 13)) { + if (TokenMatch(mFilePtr, "MESH_NUMFACES", 13)) { ParseLV4MeshLong(iNumFaces); continue; } // Number of UVWed faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVFACES", 15)) { ParseLV4MeshLong(iNumTFaces); continue; } // Number of colored faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMCVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMCVFACES", 15)) { ParseLV4MeshLong(iNumCFaces); continue; } // mesh vertex list block - if (TokenMatch(filePtr, "MESH_VERTEX_LIST", 16)) { + if (TokenMatch(mFilePtr, "MESH_VERTEX_LIST", 16)) { ParseLV3MeshVertexListBlock(iNumVertices, mesh); continue; } // mesh face list block - if (TokenMatch(filePtr, "MESH_FACE_LIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_FACE_LIST", 14)) { ParseLV3MeshFaceListBlock(iNumFaces, mesh); continue; } // mesh texture vertex list block - if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TVERTLIST", 14)) { ParseLV3MeshTListBlock(iNumTVertices, mesh); continue; } // mesh texture face block - if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TFACELIST", 14)) { ParseLV3MeshTFaceListBlock(iNumTFaces, mesh); continue; } // mesh color vertex list block - if (TokenMatch(filePtr, "MESH_CVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_CVERTLIST", 14)) { ParseLV3MeshCListBlock(iNumCVertices, mesh); continue; } // mesh color face block - if (TokenMatch(filePtr, "MESH_CFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_CFACELIST", 14)) { ParseLV3MeshCFaceListBlock(iNumCFaces, mesh); continue; } // mesh normals - if (TokenMatch(filePtr, "MESH_NORMALS", 12)) { + if (TokenMatch(mFilePtr, "MESH_NORMALS", 12)) { ParseLV3MeshNormalListBlock(mesh); continue; } // another mesh UV channel ... - if (TokenMatch(filePtr, "MESH_MAPPINGCHANNEL", 19)) { + if (TokenMatch(mFilePtr, "MESH_MAPPINGCHANNEL", 19)) { unsigned int iIndex(0); ParseLV4MeshLong(iIndex); if (0 == iIndex) { @@ -1295,7 +1319,7 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { } } // mesh animation keyframe. Not supported - if (TokenMatch(filePtr, "MESH_ANIMATION", 14)) { + if (TokenMatch(mFilePtr, "MESH_ANIMATION", 14)) { LogWarning("Found *MESH_ANIMATION element in ASE/ASK file. " "Keyframe animation is not supported by Assimp, this element " @@ -1303,14 +1327,13 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { //SkipSection(); continue; } - if (TokenMatch(filePtr, "MESH_WEIGHTS", 12)) { + if (TokenMatch(mFilePtr, "MESH_WEIGHTS", 12)) { ParseLV3MeshWeightsBlock(mesh); continue; } } AI_ASE_HANDLE_SECTION("2", "*MESH"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) { @@ -1318,47 +1341,46 @@ void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) { unsigned int iNumVertices = 0, iNumBones = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of bone vertices ... - if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + if (TokenMatch(mFilePtr, "MESH_NUMVERTEX", 14)) { ParseLV4MeshLong(iNumVertices); continue; } // Number of bones - if (TokenMatch(filePtr, "MESH_NUMBONE", 12)) { + if (TokenMatch(mFilePtr, "MESH_NUMBONE", 12)) { ParseLV4MeshLong(iNumBones); continue; } // parse the list of bones - if (TokenMatch(filePtr, "MESH_BONE_LIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_BONE_LIST", 14)) { ParseLV4MeshBones(iNumBones, mesh); continue; } // parse the list of bones vertices - if (TokenMatch(filePtr, "MESH_BONE_VERTEX_LIST", 21)) { + if (TokenMatch(mFilePtr, "MESH_BONE_VERTEX_LIST", 21)) { ParseLV4MeshBonesVertices(iNumVertices, mesh); continue; } } AI_ASE_HANDLE_SECTION("3", "*MESH_WEIGHTS"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshBones(unsigned int iNumBones, ASE::Mesh &mesh) { AI_ASE_PARSER_INIT(); mesh.mBones.resize(iNumBones, Bone("UNNAMED")); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Mesh bone with name ... - if (TokenMatch(filePtr, "MESH_BONE_NAME", 14)) { + if (TokenMatch(mFilePtr, "MESH_BONE_NAME", 14)) { // parse an index ... - if (SkipSpaces(&filePtr)) { - unsigned int iIndex = strtoul10(filePtr, &filePtr); + if (SkipSpaces(&mFilePtr, mEnd)) { + unsigned int iIndex = strtoul10(mFilePtr, &mFilePtr); if (iIndex >= iNumBones) { LogWarning("Bone index is out of bounds"); continue; @@ -1377,13 +1399,13 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes AI_ASE_PARSER_INIT(); mesh.mBoneVertices.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Mesh bone vertex - if (TokenMatch(filePtr, "MESH_BONE_VERTEX", 16)) { + if (TokenMatch(mFilePtr, "MESH_BONE_VERTEX", 16)) { // read the vertex index - unsigned int iIndex = strtoul10(filePtr, &filePtr); + unsigned int iIndex = strtoul10(mFilePtr, &mFilePtr); if (iIndex >= mesh.mPositions.size()) { iIndex = (unsigned int)mesh.mPositions.size() - 1; LogWarning("Bone vertex index is out of bounds. Using the largest valid " @@ -1392,17 +1414,17 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes // --- ignored ai_real afVert[3]; - ParseLV4MeshFloatTriple(afVert); + ParseLV4MeshRealTriple(afVert); std::pair pairOut; while (true) { // first parse the bone index ... - if (!SkipSpaces(&filePtr)) break; - pairOut.first = strtoul10(filePtr, &filePtr); + if (!SkipSpaces(&mFilePtr, mEnd)) break; + pairOut.first = strtoul10(mFilePtr, &mFilePtr); // then parse the vertex weight - if (!SkipSpaces(&filePtr)) break; - filePtr = fast_atoreal_move(filePtr, pairOut.second); + if (!SkipSpaces(&mFilePtr, mEnd)) break; + mFilePtr = fast_atoreal_move(mFilePtr, pairOut.second); // -1 marks unused entries if (-1 != pairOut.first) { @@ -1414,7 +1436,6 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes } AI_ASE_HANDLE_SECTION("4", "*MESH_BONE_VERTEX"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshVertexListBlock( @@ -1424,15 +1445,15 @@ void Parser::ParseLV3MeshVertexListBlock( // allocate enough storage in the array mesh.mPositions.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_VERTEX", 11)) { + if (TokenMatch(mFilePtr, "MESH_VERTEX", 11)) { aiVector3D vTemp; unsigned int iIndex; - ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + ParseLV4MeshRealTriple(&vTemp.x, iIndex); if (iIndex >= iNumVertices) { LogWarning("Invalid vertex index. It will be ignored"); @@ -1443,7 +1464,6 @@ void Parser::ParseLV3MeshVertexListBlock( } AI_ASE_HANDLE_SECTION("3", "*MESH_VERTEX_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) { @@ -1452,11 +1472,11 @@ void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) // allocate enough storage in the face array mesh.mFaces.resize(iNumFaces); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_FACE", 9)) { + if (TokenMatch(mFilePtr, "MESH_FACE", 9)) { ASE::Face mFace; ParseLV4MeshFace(mFace); @@ -1470,7 +1490,6 @@ void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) } AI_ASE_HANDLE_SECTION("3", "*MESH_FACE_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, @@ -1480,14 +1499,14 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, // allocate enough storage in the array mesh.amTexCoords[iChannel].resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_TVERT", 10)) { + if (TokenMatch(mFilePtr, "MESH_TVERT", 10)) { aiVector3D vTemp; unsigned int iIndex; - ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + ParseLV4MeshRealTriple(&vTemp.x, iIndex); if (iIndex >= iNumVertices) { LogWarning("Tvertex has an invalid index. It will be ignored"); @@ -1503,18 +1522,17 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, } AI_ASE_HANDLE_SECTION("3", "*MESH_TVERT_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh, unsigned int iChannel) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_TFACE", 10)) { + if (TokenMatch(mFilePtr, "MESH_TFACE", 10)) { unsigned int aiValues[3]; unsigned int iIndex = 0; @@ -1532,7 +1550,6 @@ void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces, } AI_ASE_HANDLE_SECTION("3", "*MESH_TFACE_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) { @@ -1541,33 +1558,32 @@ void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) { unsigned int iNumTVertices = 0; unsigned int iNumTFaces = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of texture coordinates in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVERTEX", 15)) { ParseLV4MeshLong(iNumTVertices); continue; } // Number of UVWed faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVFACES", 15)) { ParseLV4MeshLong(iNumTFaces); continue; } // mesh texture vertex list block - if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TVERTLIST", 14)) { ParseLV3MeshTListBlock(iNumTVertices, mesh, iChannel); continue; } // mesh texture face block - if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TFACELIST", 14)) { ParseLV3MeshTFaceListBlock(iNumTFaces, mesh, iChannel); continue; } } AI_ASE_HANDLE_SECTION("3", "*MESH_MAPPING_CHANNEL"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) { @@ -1576,11 +1592,11 @@ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) // allocate enough storage in the array mesh.mVertexColors.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_VERTCOL", 12)) { + if (TokenMatch(mFilePtr, "MESH_VERTCOL", 12)) { aiColor4D vTemp; vTemp.a = 1.0f; unsigned int iIndex; @@ -1595,17 +1611,16 @@ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) } AI_ASE_HANDLE_SECTION("3", "*MESH_CVERTEX_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_CFACE", 10)) { + if (TokenMatch(mFilePtr, "MESH_CFACE", 10)) { unsigned int aiValues[3]; unsigned int iIndex = 0; @@ -1623,7 +1638,6 @@ void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) } AI_ASE_HANDLE_SECTION("3", "*MESH_CFACE_LIST"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { @@ -1639,11 +1653,11 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { // Smooth the vertex and face normals together. The result // will be edgy then, but otherwise everything would be soft ... while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (faceIdx != UINT_MAX && TokenMatch(filePtr, "MESH_VERTEXNORMAL", 17)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (faceIdx != UINT_MAX && TokenMatch(mFilePtr, "MESH_VERTEXNORMAL", 17)) { aiVector3D vNormal; - ParseLV4MeshFloatTriple(&vNormal.x, index); + ParseLV4MeshRealTriple(&vNormal.x, index); if (faceIdx >= sMesh.mFaces.size()) continue; @@ -1663,9 +1677,9 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { sMesh.mNormals[faceIdx * 3 + index] += vNormal; continue; } - if (TokenMatch(filePtr, "MESH_FACENORMAL", 15)) { + if (TokenMatch(mFilePtr, "MESH_FACENORMAL", 15)) { aiVector3D vNormal; - ParseLV4MeshFloatTriple(&vNormal.x, faceIdx); + ParseLV4MeshRealTriple(&vNormal.x, faceIdx); if (faceIdx >= sMesh.mFaces.size()) { ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_FACENORMAL section"); @@ -1681,39 +1695,38 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { } AI_ASE_HANDLE_SECTION("3", "*MESH_NORMALS"); } - return; } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshFace(ASE::Face &out) { // skip spaces and tabs - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL [#1]"); SkipToNextToken(); return; } // parse the face index - out.iFace = strtoul10(filePtr, &filePtr); + out.iFace = strtoul10(mFilePtr, &mFilePtr); // next character should be ':' - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // FIX: there are some ASE files which haven't got : here .... LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. \':\' expected [#2]"); SkipToNextToken(); return; } // FIX: There are some ASE files which haven't got ':' here - if (':' == *filePtr) ++filePtr; + if (':' == *mFilePtr) ++mFilePtr; // Parse all mesh indices for (unsigned int i = 0; i < 3; ++i) { unsigned int iIndex = 0; - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL"); SkipToNextToken(); return; } - switch (*filePtr) { + switch (*mFilePtr) { case 'A': case 'a': break; @@ -1731,39 +1744,39 @@ void Parser::ParseLV4MeshFace(ASE::Face &out) { SkipToNextToken(); return; }; - ++filePtr; + ++mFilePtr; // next character should be ':' - if (!SkipSpaces(&filePtr) || ':' != *filePtr) { + if (!SkipSpaces(&mFilePtr, mEnd) || ':' != *mFilePtr) { LogWarning("Unable to parse *MESH_FACE Element: " "Unexpected EOL. \':\' expected [#2]"); SkipToNextToken(); return; } - ++filePtr; - if (!SkipSpaces(&filePtr)) { + ++mFilePtr; + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " - "Vertex index ecpected [#4]"); + "Vertex index expected [#4]"); SkipToNextToken(); return; } - out.mIndices[iIndex] = strtoul10(filePtr, &filePtr); + out.mIndices[iIndex] = strtoul10(mFilePtr, &mFilePtr); } // now we need to skip the AB, BC, CA blocks. while (true) { - if ('*' == *filePtr) break; - if (IsLineEnd(*filePtr)) { + if ('*' == *mFilePtr) break; + if (IsLineEnd(*mFilePtr)) { //iLineNumber++; return; } - filePtr++; + mFilePtr++; } // parse the smoothing group of the face - if (TokenMatch(filePtr, "*MESH_SMOOTHING", 15)) { - if (!SkipSpaces(&filePtr)) { + if (TokenMatch(mFilePtr, "*MESH_SMOOTHING", 15)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_SMOOTHING Element: " "Unexpected EOL. Smoothing group(s) expected [#5]"); SkipToNextToken(); @@ -1773,37 +1786,43 @@ void Parser::ParseLV4MeshFace(ASE::Face &out) { // Parse smoothing groups until we don't anymore see commas // FIX: There needn't always be a value, sad but true while (true) { - if (*filePtr < '9' && *filePtr >= '0') { - out.iSmoothGroup |= (1 << strtoul10(filePtr, &filePtr)); + if (*mFilePtr < '9' && *mFilePtr >= '0') { + uint32_t value = strtoul10(mFilePtr, &mFilePtr); + if (value < 32) { + out.iSmoothGroup |= (1 << strtoul10(mFilePtr, &mFilePtr)); + } else { + const std::string message = std::string("Unable to set smooth group, value with ") + ai_to_string(value) + std::string(" out of range"); + LogWarning(message.c_str()); + } } - SkipSpaces(&filePtr); - if (',' != *filePtr) { + SkipSpaces(&mFilePtr, mEnd); + if (',' != *mFilePtr) { break; } - ++filePtr; - SkipSpaces(&filePtr); + ++mFilePtr; + SkipSpaces(&mFilePtr, mEnd); } } // *MESH_MTLID is optional, too while (true) { - if ('*' == *filePtr) { + if ('*' == *mFilePtr) { break; } - if (IsLineEnd(*filePtr)) { + if (IsLineEnd(*mFilePtr)) { return; } - filePtr++; + mFilePtr++; } - if (TokenMatch(filePtr, "*MESH_MTLID", 11)) { - if (!SkipSpaces(&filePtr)) { + if (TokenMatch(mFilePtr, "*MESH_MTLID", 11)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_MTLID Element: Unexpected EOL. " "Material index expected [#6]"); SkipToNextToken(); return; } - out.iMaterial = strtoul10(filePtr, &filePtr); + out.iMaterial = strtoul10(mFilePtr, &mFilePtr); } return; } @@ -1825,7 +1844,17 @@ void Parser::ParseLV4MeshLongTriple(unsigned int *apOut, unsigned int &rIndexOut ParseLV4MeshLongTriple(apOut); } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut) { +void Parser::ParseLV4MeshRealTriple(ai_real *apOut, unsigned int &rIndexOut) { + ai_assert(nullptr != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshRealTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(float* apOut, unsigned int& rIndexOut) { ai_assert(nullptr != apOut); // parse the index @@ -1835,7 +1864,15 @@ void Parser::ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut) { ParseLV4MeshFloatTriple(apOut); } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { +void Parser::ParseLV4MeshRealTriple(ai_real *apOut) { + ai_assert(nullptr != apOut); + + for (unsigned int i = 0; i < 3; ++i) { + ParseLV4MeshReal(apOut[i]); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(float* apOut) { ai_assert(nullptr != apOut); for (unsigned int i = 0; i < 3; ++i) { @@ -1843,9 +1880,9 @@ void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { } } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloat(ai_real &fOut) { +void Parser::ParseLV4MeshReal(ai_real &fOut) { // skip spaces and tabs - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // LOG LogWarning("Unable to parse float: unexpected EOL [#1]"); fOut = 0.0; @@ -1853,12 +1890,25 @@ void Parser::ParseLV4MeshFloat(ai_real &fOut) { return; } // parse the first float - filePtr = fast_atoreal_move(filePtr, fOut); + mFilePtr = fast_atoreal_move(mFilePtr, fOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloat(float &fOut) { + // skip spaces and tabs + if (!SkipSpaces(&mFilePtr, mEnd)) { + // LOG + LogWarning("Unable to parse float: unexpected EOL [#1]"); + fOut = 0.0; + ++iLineNumber; + return; + } + // parse the first float + mFilePtr = fast_atoreal_move(mFilePtr, fOut); } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshLong(unsigned int &iOut) { // Skip spaces and tabs - if (!SkipSpaces(&filePtr)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // LOG LogWarning("Unable to parse long: unexpected EOL [#1]"); iOut = 0; @@ -1866,7 +1916,9 @@ void Parser::ParseLV4MeshLong(unsigned int &iOut) { return; } // parse the value - iOut = strtoul10(filePtr, &filePtr); + iOut = strtoul10(mFilePtr, &mFilePtr); +} + } #endif // ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.h b/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.h index 8cda32f24..916605790 100644 --- a/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.h +++ b/Engine/lib/assimp/code/AssetLib/ASE/ASEParser.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -385,20 +384,18 @@ struct Dummy : public BaseNode { /** \brief Class to parse ASE files */ class Parser { -private: - Parser() AI_NO_EXCEPT { - // empty - } - public: + /// @brief No default constructor. + Parser() = delete; + // ------------------------------------------------------------------- //! Construct a parser from a given input file which is //! guaranteed to be terminated with zero. - //! @param szFile Input file + //! @param file The name of the input file. //! @param fileFormatDefault Assumed file format version. If the //! file format is specified in the file the new value replaces //! the default value. - Parser(const char *szFile, unsigned int fileFormatDefault); + Parser(const char *file, size_t fileLen, unsigned int fileFormatDefault); // ------------------------------------------------------------------- //! Parses the file into the parsers internal representation @@ -556,13 +553,15 @@ private: //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) //! \param apOut Output buffer (3 floats) //! \param rIndexOut Output index - void ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut); + void ParseLV4MeshRealTriple(ai_real *apOut, unsigned int &rIndexOut); + void ParseLV4MeshFloatTriple(float *apOut, unsigned int &rIndexOut); // ------------------------------------------------------------------- //! Parse a *MESH_VERT block in a file //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) //! \param apOut Output buffer (3 floats) - void ParseLV4MeshFloatTriple(ai_real *apOut); + void ParseLV4MeshRealTriple(ai_real *apOut); + void ParseLV4MeshFloatTriple(float *apOut); // ------------------------------------------------------------------- //! Parse a *MESH_TFACE block in a file @@ -580,7 +579,8 @@ private: // ------------------------------------------------------------------- //! Parse a single float element //! \param fOut Output float - void ParseLV4MeshFloat(ai_real &fOut); + void ParseLV4MeshReal(ai_real &fOut); + void ParseLV4MeshFloat(float &fOut); // ------------------------------------------------------------------- //! Parse a single int element @@ -620,8 +620,8 @@ private: bool ParseString(std::string &out, const char *szName); public: - //! Pointer to current data - const char *filePtr; + const char *mFilePtr; ////< Pointer to current data + const char *mEnd; ///< The end pointer of the file data //! background color to be passed to the viewer //! QNAN if none was found diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.cpp b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.cpp index 149b3c5f3..b8465f866 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.h b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.h index 1801c1680..271b6b833 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,5 +56,5 @@ namespace Assimp { void ASSIMP_API ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/); } -#endif +#endif #endif // AI_ASSBINEXPORTER_H_INC diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp index 97be634de..90bcccf90 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -50,11 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#ifdef ASSIMP_BUILD_NO_OWN_ZLIB -#include -#else -#include "../contrib/zlib/zlib.h" -#endif +#include "zlib.h" #include @@ -291,15 +287,15 @@ public: size_t Read(void * /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { return 0; } - + aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) override { return aiReturn_FAILURE; } - + size_t Tell() const override { return cursor; } - + void Flush() override { // not implemented } diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.h b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.h index cfed3b400..84641df46 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.h +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinFileWriter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.cpp b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.cpp index f7b35636c..70e8a06c2 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -65,7 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Assimp Binary Importer", "Gargaj / Conspiracy", "", @@ -91,9 +91,13 @@ bool AssbinImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, boo } char s[32]; - in->Read(s, sizeof(char), 32); + const size_t read = in->Read(s, sizeof(char), 32); pIOHandler->Close(in); + + if (read < 19) { + return false; + } return strncmp(s, "ASSIMP.binary-dump.", 19) == 0; } diff --git a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.h b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.h index f922b91fd..2b85e6655 100644 --- a/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Assbin/AssbinLoader.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Assjson/cencode.c b/Engine/lib/assimp/code/AssetLib/Assjson/cencode.c index 614a2671f..ec771536d 100644 --- a/Engine/lib/assimp/code/AssetLib/Assjson/cencode.c +++ b/Engine/lib/assimp/code/AssetLib/Assjson/cencode.c @@ -7,7 +7,7 @@ For details, see http://sourceforge.net/projects/libb64 #include "cencode.h" // changed from -const int CHARS_PER_LINE = 72; +static const int CHARS_PER_LINE = 72; #ifdef _MSC_VER #pragma warning(push) diff --git a/Engine/lib/assimp/code/AssetLib/Assjson/json_exporter.cpp b/Engine/lib/assimp/code/AssetLib/Assjson/json_exporter.cpp index 7b2c8ec81..ea5194fb0 100644 --- a/Engine/lib/assimp/code/AssetLib/Assjson/json_exporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assjson/json_exporter.cpp @@ -10,6 +10,7 @@ Licensed under a 3-clause BSD license. See the LICENSE file for more information #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER #include +#include #include #include #include @@ -23,16 +24,15 @@ Licensed under a 3-clause BSD license. See the LICENSE file for more information #define CURRENT_FORMAT_VERSION 100 -// grab scoped_ptr from assimp to avoid a dependency on boost. -//#include - #include "mesh_splitter.h" extern "C" { -#include "cencode.h" +# include "cencode.h" } + namespace Assimp { +// Forward declarations void ExportAssimp2Json(const char *, Assimp::IOSystem *, const aiScene *, const Assimp::ExportProperties *); // small utility class to simplify serializing the aiScene to Json @@ -43,7 +43,7 @@ public: Flag_WriteSpecialFloats = 0x2, Flag_SkipWhitespaces = 0x4 }; - + JSONWriter(Assimp::IOStream &out, unsigned int flags = 0u) : out(out), indent (""), newline("\n"), space(" "), buff (), first(false), flags(flags) { // make sure that all formatting happens using the standard, C locale and not the user's current locale @@ -179,7 +179,6 @@ private: // escape backslashes and single quotes, both would render the JSON invalid if left as is t.reserve(s.length); for (size_t i = 0; i < s.length; ++i) { - if (s.data[i] == '\\' || s.data[i] == '\'' || s.data[i] == '\"') { t.push_back('\\'); } @@ -241,7 +240,7 @@ private: unsigned int flags; }; -void Write(JSONWriter &out, const aiVector3D &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiVector3D &ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.x); out.Element(ai.y); @@ -249,7 +248,7 @@ void Write(JSONWriter &out, const aiVector3D &ai, bool is_elem = true) { out.EndArray(); } -void Write(JSONWriter &out, const aiQuaternion &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiQuaternion &ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.w); out.Element(ai.x); @@ -258,7 +257,7 @@ void Write(JSONWriter &out, const aiQuaternion &ai, bool is_elem = true) { out.EndArray(); } -void Write(JSONWriter &out, const aiColor3D &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiColor3D &ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.r); out.Element(ai.g); @@ -266,7 +265,7 @@ void Write(JSONWriter &out, const aiColor3D &ai, bool is_elem = true) { out.EndArray(); } -void Write(JSONWriter &out, const aiMatrix4x4 &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiMatrix4x4 &ai, bool is_elem = true) { out.StartArray(is_elem); for (unsigned int x = 0; x < 4; ++x) { for (unsigned int y = 0; y < 4; ++y) { @@ -276,7 +275,7 @@ void Write(JSONWriter &out, const aiMatrix4x4 &ai, bool is_elem = true) { out.EndArray(); } -void Write(JSONWriter &out, const aiBone &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiBone &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -297,7 +296,7 @@ void Write(JSONWriter &out, const aiBone &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiFace &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiFace &ai, bool is_elem = true) { out.StartArray(is_elem); for (unsigned int i = 0; i < ai.mNumIndices; ++i) { out.Element(ai.mIndices[i]); @@ -305,7 +304,7 @@ void Write(JSONWriter &out, const aiFace &ai, bool is_elem = true) { out.EndArray(); } -void Write(JSONWriter &out, const aiMesh &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiMesh &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -416,7 +415,7 @@ void Write(JSONWriter &out, const aiMesh &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiNode &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiNode &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -446,7 +445,7 @@ void Write(JSONWriter &out, const aiNode &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("properties"); @@ -466,41 +465,55 @@ void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) { out.Key("value"); switch (prop->mType) { - case aiPTI_Float: - if (prop->mDataLength / sizeof(float) > 1) { - out.StartArray(); - for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(float); ++ii) { - out.Element(reinterpret_cast(prop->mData)[ii]); + case aiPTI_Float: + if (prop->mDataLength / sizeof(float) > 1) { + out.StartArray(); + for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(float); ++ii) { + out.Element(reinterpret_cast(prop->mData)[ii]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast(prop->mData)); } - out.EndArray(); - } else { - out.SimpleValue(*reinterpret_cast(prop->mData)); - } - break; - - case aiPTI_Integer: - if (prop->mDataLength / sizeof(int) > 1) { - out.StartArray(); - for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(int); ++ii) { - out.Element(reinterpret_cast(prop->mData)[ii]); + break; + case aiPTI_Double: + if (prop->mDataLength / sizeof(double) > 1) { + out.StartArray(); + for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(double); ++ii) { + out.Element(reinterpret_cast(prop->mData)[ii]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast(prop->mData)); } - out.EndArray(); - } else { - out.SimpleValue(*reinterpret_cast(prop->mData)); - } - break; + break; + case aiPTI_Integer: + if (prop->mDataLength / sizeof(int) > 1) { + out.StartArray(); + for (unsigned int ii = 0; ii < prop->mDataLength / sizeof(int); ++ii) { + out.Element(reinterpret_cast(prop->mData)[ii]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast(prop->mData)); + } + break; - case aiPTI_String: { - aiString s; - aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s); - out.SimpleValue(s); - } break; - case aiPTI_Buffer: { - // binary data is written as series of hex-encoded octets - out.SimpleValue(prop->mData, prop->mDataLength); - } break; - default: - assert(false); + case aiPTI_String: + { + aiString s; + aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s); + out.SimpleValue(s); + } + break; + case aiPTI_Buffer: + { + // binary data is written as series of hex-encoded octets + out.SimpleValue(prop->mData, prop->mDataLength); + } + break; + default: + ai_assert(false); } out.EndObj(); @@ -510,7 +523,7 @@ void Write(JSONWriter &out, const aiMaterial &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiTexture &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiTexture &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("width"); @@ -546,7 +559,7 @@ void Write(JSONWriter &out, const aiTexture &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiLight &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiLight &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -594,7 +607,7 @@ void Write(JSONWriter &out, const aiLight &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiNodeAnim &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiNodeAnim &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -647,7 +660,7 @@ void Write(JSONWriter &out, const aiNodeAnim &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiAnimation &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiAnimation &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -668,7 +681,7 @@ void Write(JSONWriter &out, const aiAnimation &ai, bool is_elem = true) { out.EndObj(); } -void Write(JSONWriter &out, const aiCamera &ai, bool is_elem = true) { +static void Write(JSONWriter &out, const aiCamera &ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); @@ -695,7 +708,7 @@ void Write(JSONWriter &out, const aiCamera &ai, bool is_elem = true) { out.EndObj(); } -void WriteFormatInfo(JSONWriter &out) { +static void WriteFormatInfo(JSONWriter &out) { out.StartObj(); out.Key("format"); out.SimpleValue("\"assimp2json\""); @@ -704,7 +717,7 @@ void WriteFormatInfo(JSONWriter &out) { out.EndObj(); } -void Write(JSONWriter &out, const aiScene &ai) { +static void Write(JSONWriter &out, const aiScene &ai) { out.StartObj(); out.Key("__metadata__"); diff --git a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp index 731916a25..b9691b822 100644 --- a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.h b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.h index 6fcdebfab..28a9b7f35 100644 --- a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp index a443e3fe9..f6fdc4a0c 100644 --- a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -652,7 +652,7 @@ void DumpSceneToAssxml( const char *pFile, const char *cmd, IOSystem *pIOSystem, const aiScene *pScene, bool shortened) { std::unique_ptr file(pIOSystem->Open(pFile, "wt")); - if (!file.get()) { + if (!file) { throw std::runtime_error("Unable to open output file " + std::string(pFile) + '\n'); } diff --git a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h index 0620c9db7..1051a03a0 100644 --- a/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h +++ b/Engine/lib/assimp/code/AssetLib/Assxml/AssxmlFileWriter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.cpp b/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.cpp index 68aa7d4d4..d0029277c 100644 --- a/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,10 +59,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -using namespace Assimp; +namespace Assimp { using namespace std; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "BlitzBasic 3D Importer", "", "", @@ -79,9 +79,9 @@ static const aiImporterDesc desc = { #pragma warning(disable : 4018) #endif -//#define DEBUG_B3D +// #define DEBUG_B3D -template +template void DeleteAllBarePointers(std::vector &x) { for (auto p : x) { delete p; @@ -116,7 +116,7 @@ void B3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::unique_ptr file(pIOHandler->Open(pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open B3D file ", pFile, "."); } @@ -150,7 +150,7 @@ AI_WONT_RETURN void B3DImporter::Fail(const string &str) { // ------------------------------------------------------------------------------------------------ int B3DImporter::ReadByte() { - if (_pos > _buf.size()) { + if (_pos >= _buf.size()) { Fail("EOF"); } @@ -253,7 +253,7 @@ size_t B3DImporter::ChunkSize() { template T *B3DImporter::to_array(const vector &v) { if (v.empty()) { - return 0; + return nullptr; } T *p = new T[v.size()]; for (size_t i = 0; i < v.size(); ++i) { @@ -266,7 +266,7 @@ T *B3DImporter::to_array(const vector &v) { template T **unique_to_array(vector> &v) { if (v.empty()) { - return 0; + return nullptr; } T **p = new T *[v.size()]; for (size_t i = 0; i < v.size(); ++i) { @@ -329,7 +329,7 @@ void B3DImporter::ReadBRUS() { mat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED); } - //Textures + // Textures for (int i = 0; i < n_texs; ++i) { int texid = ReadInt(); if (texid < -1 || (texid >= 0 && texid >= static_cast(_textures.size()))) { @@ -372,7 +372,7 @@ void B3DImporter::ReadVRTS() { } if (_vflags & 2) { - ReadQuat(); //skip v 4bytes... + ReadQuat(); // skip v 4bytes... } for (int j = 0; j < _tcsets; ++j) { @@ -418,7 +418,6 @@ void B3DImporter::ReadTRIS(int v0) { ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2); #endif Fail("Bad triangle index"); - continue; } face->mNumIndices = 3; face->mIndices = new unsigned[3]; @@ -617,7 +616,7 @@ void B3DImporter::ReadBB3D(aiScene *scene) { } else if (chunk == "BRUS") { ReadBRUS(); } else if (chunk == "NODE") { - ReadNODE(0); + ReadNODE(nullptr); } ExitChunk(); } @@ -642,7 +641,7 @@ void B3DImporter::ReadBB3D(aiScene *scene) { int n_tris = mesh->mNumFaces; int n_verts = mesh->mNumVertices = n_tris * 3; - aiVector3D *mv = mesh->mVertices = new aiVector3D[n_verts], *mn = 0, *mc = 0; + aiVector3D *mv = mesh->mVertices = new aiVector3D[n_verts], *mn = nullptr, *mc = nullptr; if (_vflags & 1) { mn = mesh->mNormals = new aiVector3D[n_verts]; } @@ -705,22 +704,22 @@ void B3DImporter::ReadBB3D(aiScene *scene) { } } - //nodes + // nodes scene->mRootNode = _nodes[0]; _nodes.clear(); // node ownership now belongs to scene - //material + // material if (!_materials.size()) { _materials.emplace_back(std::unique_ptr(new aiMaterial)); } scene->mNumMaterials = static_cast(_materials.size()); scene->mMaterials = unique_to_array(_materials); - //meshes + // meshes scene->mNumMeshes = static_cast(_meshes.size()); scene->mMeshes = unique_to_array(_meshes); - //animations + // animations if (_animations.size() == 1 && _nodeAnims.size()) { aiAnimation *anim = _animations.back().get(); @@ -739,4 +738,6 @@ void B3DImporter::ReadBB3D(aiScene *scene) { flip.Execute(scene); } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_B3D_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.h b/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.h index e47d9078b..0fcfae620 100644 --- a/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.h +++ b/Engine/lib/assimp/code/AssetLib/B3D/B3DImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.cpp b/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.cpp index 0ce60ea1c..2b37286ea 100644 --- a/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.cpp @@ -4,7 +4,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -55,10 +55,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { + using namespace Assimp::Formatter; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "BVH Importer (MoCap)", "", "", @@ -73,8 +74,8 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Aborts the file reading with an exception -template -AI_WONT_RETURN void BVHLoader::ThrowException(T&&... args) { +template +AI_WONT_RETURN void BVHLoader::ThrowException(T &&...args) { throw DeadlyImportError(mFileName, ":", mLine, " - ", args...); } @@ -115,7 +116,7 @@ void BVHLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IOSyst // read file into memory std::unique_ptr file(pIOHandler->Open(pFile)); - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open file ", pFile, "."); } @@ -191,7 +192,7 @@ aiNode *BVHLoader::ReadNode() { // now read the node's contents std::string siteToken; - while (1) { + while (true) { std::string token = GetNextToken(); // node offset to parent node @@ -247,7 +248,7 @@ aiNode *BVHLoader::ReadEndSite(const std::string &pParentName) { // now read the node's contents. Only possible entry is "OFFSET" std::string token; - while (1) { + while (true) { token.clear(); token = GetNextToken(); @@ -426,7 +427,7 @@ void BVHLoader::CreateAnimation(aiScene *pScene) { nodeAnim->mNodeName.Set(nodeName); std::map channelMap; - //Build map of channels + // Build map of channels for (unsigned int channel = 0; channel < node.mChannels.size(); ++channel) { channelMap[node.mChannels[channel]] = channel; } @@ -441,7 +442,7 @@ void BVHLoader::CreateAnimation(aiScene *pScene) { // Now compute all translations for (BVHLoader::ChannelType channel = Channel_PositionX; channel <= Channel_PositionZ; channel = (BVHLoader::ChannelType)(channel + 1)) { - //Find channel in node + // Find channel in node std::map::iterator mapIter = channelMap.find(channel); if (mapIter == channelMap.end()) @@ -485,30 +486,27 @@ void BVHLoader::CreateAnimation(aiScene *pScene) { for (unsigned int fr = 0; fr < mAnimNumFrames; ++fr) { aiMatrix4x4 temp; aiMatrix3x3 rotMatrix; - for (unsigned int channelIdx = 0; channelIdx < node.mChannels.size(); ++ channelIdx) { - switch (node.mChannels[channelIdx]) { - case Channel_RotationX: - { + for (unsigned int channelIdx = 0; channelIdx < node.mChannels.size(); ++channelIdx) { + switch (node.mChannels[channelIdx]) { + case Channel_RotationX: { const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; - aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); - } - break; - case Channel_RotationY: - { + aiMatrix4x4::RotationX(angle, temp); + rotMatrix *= aiMatrix3x3(temp); + } break; + case Channel_RotationY: { const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; - aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp); - } - break; - case Channel_RotationZ: - { + aiMatrix4x4::RotationY(angle, temp); + rotMatrix *= aiMatrix3x3(temp); + } break; + case Channel_RotationZ: { const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; - aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); - } - break; + aiMatrix4x4::RotationZ(angle, temp); + rotMatrix *= aiMatrix3x3(temp); + } break; default: break; - } - } + } + } rotkey->mTime = double(fr); rotkey->mValue = aiQuaternion(rotMatrix); ++rotkey; @@ -525,4 +523,6 @@ void BVHLoader::CreateAnimation(aiScene *pScene) { } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.h b/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.h index 56c32bd99..2c5e24114 100644 --- a/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.h +++ b/Engine/lib/assimp/code/AssetLib/BVH/BVHLoader.h @@ -4,7 +4,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.cpp index be536ebdb..0660967bd 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -52,8 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { template <> const char *LogFunctions::Prefix() { - static auto prefix = "BLEND_BMESH: "; - return prefix; + return "BLEND_BMESH: "; } } // namespace Assimp @@ -140,7 +139,7 @@ void BlenderBMeshConverter::ConvertPolyToFaces(const MPoly &poly) { ThrowException("BMesh uv loop array has incorrect size"); } const MLoopUV *loopUV = &BMesh->mloopuv[poly.loopstart]; - AddTFace(loopUV[0].uv, loopUV[1].uv, loopUV[2].uv, poly.totloop == 4 ? loopUV[3].uv : 0); + AddTFace(loopUV[0].uv, loopUV[1].uv, loopUV[2].uv, poly.totloop == 4 ? loopUV[3].uv : nullptr); } } else if (poly.totloop > 4) { #if ASSIMP_BLEND_WITH_GLU_TESSELLATE diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.h index 45ca2c806..1798aaf74 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderBMesh.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -80,7 +80,7 @@ namespace Assimp void DestroyTriMesh( ); void ConvertPolyToFaces( const Blender::MPoly& poly ); void AddFace( int v1, int v2, int v3, int v4 = 0 ); - void AddTFace( const float* uv1, const float* uv2, const float *uv3, const float* uv4 = 0 ); + void AddTFace(const float *uv1, const float *uv2, const float *uv3, const float *uv4 = nullptr); const Blender::Mesh* BMesh; Blender::Mesh* triMesh; diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderCustomData.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderCustomData.cpp index c74a6bb75..2359482e1 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderCustomData.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderCustomData.cpp @@ -96,7 +96,8 @@ struct CustomDataTypeDescription { * other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures * use a special readfunction for that cases */ -std::array customDataTypeDescriptions = { { DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert), +static std::array customDataTypeDescriptions = { { + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert), DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge), diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.cpp index fbb61ab4f..311911249 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -72,7 +72,7 @@ struct Type { // ------------------------------------------------------------------------------------------------ void DNAParser::Parse() { - StreamReaderAny &stream = *db.reader.get(); + StreamReaderAny &stream = *db.reader; DNA &dna = db.dna; if (!match4(stream, "SDNA")) { diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.h index dc5a36c2a..f6a691fd6 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -106,9 +106,7 @@ struct ElemBase { // empty } - virtual ~ElemBase() { - // empty - } + virtual ~ElemBase() = default; /** Type name of the element. The type * string points is the `c_str` of the `name` attribute of the @@ -431,6 +429,17 @@ inline bool Structure ::ResolvePointer(std::shared_pt const Field &f, bool) const; +template <> bool Structure :: ResolvePointer( + std::shared_ptr& out, const Pointer & ptrval, + const FileDatabase& db, const Field&, bool) const; +template <> inline void Structure :: Convert (int& dest,const FileDatabase& db) const; +template<> inline void Structure :: Convert (short& dest,const FileDatabase& db) const; +template <> inline void Structure :: Convert (char& dest,const FileDatabase& db) const; +template <> inline void Structure::Convert(unsigned char& dest, const FileDatabase& db) const; +template <> inline void Structure :: Convert (float& dest,const FileDatabase& db) const; +template <> inline void Structure :: Convert (double& dest,const FileDatabase& db) const; +template <> inline void Structure :: Convert (Pointer& dest,const FileDatabase& db) const; + // ------------------------------------------------------------------------------- /** Represents the full data structure information for a single BLEND file. * This data is extracted from the DNA1 chunk in the file. diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.inl b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.inl index 4f64987a5..9bcb602ba 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.inl +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderDNA.inl @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderIntermediate.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderIntermediate.h index 546b79f09..700beb7b0 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderIntermediate.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderIntermediate.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.cpp index 9322369eb..4b913f595 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.cpp @@ -1,10 +1,8 @@ - /* Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,23 +61,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include +#include // zlib is needed for compressed blend files #ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND #include "Common/Compression.h" -/* #ifdef ASSIMP_BUILD_NO_OWN_ZLIB -# include -# else -# include "../contrib/zlib/zlib.h" -# endif*/ #endif namespace Assimp { template <> const char *LogFunctions::Prefix() { - static auto prefix = "BLEND: "; - return prefix; + return "BLEND: "; } } // namespace Assimp @@ -88,7 +82,7 @@ using namespace Assimp; using namespace Assimp::Blender; using namespace Assimp::Formatter; -static const aiImporterDesc blenderDesc = { +static constexpr aiImporterDesc blenderDesc = { "Blender 3D Importer (http://www.blender3d.org)", "", "", @@ -114,15 +108,12 @@ BlenderImporter::~BlenderImporter() { delete modifier_cache; } -static const char * const Tokens[] = { "BLENDER" }; +static const char Token[] = "BLENDER"; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { - // note: this won't catch compressed files - static const char *tokens[] = { " uncompressed; -#endif - FileDatabase file; - std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); - if (!stream) { - ThrowException("Could not open file for reading"); + StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler); + if (!streamOrError.error.empty()) { + ThrowException(streamOrError.error); } + std::shared_ptr stream = std::move(streamOrError.stream); - char magic[8] = { 0 }; - stream->Read(magic, 7, 1); - if (strcmp(magic, Tokens[0])) { - // Check for presence of the gzip header. If yes, assume it is a - // compressed blend file and try uncompressing it, else fail. This is to - // avoid uncompressing random files which our loader might end up with. -#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND - ThrowException("BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"); -#else - if (magic[0] != 0x1f || static_cast(magic[1]) != 0x8b) { - ThrowException("BLENDER magic bytes are missing, couldn't find GZIP header either"); - } + char version[4] = { 0 }; + file.i64bit = (stream->Read(version, 1, 1), version[0] == '-'); + file.little = (stream->Read(version, 1, 1), version[0] == 'v'); - LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file"); - if (magic[2] != 8) { - ThrowException("Unsupported GZIP compression method"); - } + stream->Read(version, 3, 1); + version[3] = '\0'; - // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer - stream->Seek(0L, aiOrigin_SET); - std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); - - size_t total = 0; - Compression compression; - if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) { - total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), uncompressed); - compression.close(); - } - - // replace the input stream with a memory stream - stream.reset(new MemoryIOStream(reinterpret_cast(uncompressed.data()), total)); - - // .. and retry - stream->Read(magic, 7, 1); - if (strcmp(magic, "BLENDER")) { - ThrowException("Found no BLENDER magic word in decompressed GZIP file"); - } -#endif - } - - file.i64bit = (stream->Read(magic, 1, 1), magic[0] == '-'); - file.little = (stream->Read(magic, 1, 1), magic[0] == 'v'); - - stream->Read(magic, 3, 1); - magic[3] = '\0'; - - LogInfo("Blender version is ", magic[0], ".", magic + 1, + LogInfo("Blender version is ", version[0], ".", version + 1, " (64bit: ", file.i64bit ? "true" : "false", ", little endian: ", file.little ? "true" : "false", ")"); - ParseBlendFile(file, stream); + ParseBlendFile(file, std::move(stream)); Scene scene; ExtractScene(scene, file); @@ -218,7 +167,7 @@ void BlenderImporter::ParseBlendFile(FileDatabase &out, std::shared_ptrpackedfile) { name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(conv_data.textures->size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(conv_data.textures->size())); conv_data.textures->push_back(new aiTexture()); aiTexture *curTex = conv_data.textures->back(); @@ -482,7 +431,7 @@ void BlenderImporter::AddSentinelTexture(aiMaterial *out, const Material *mat, c (void)conv_data; aiString name; - name.length = ai_snprintf(name.data, MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++, + name.length = ai_snprintf(name.data, AI_MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++, GetTextureTypeDisplayString(tex->tex->type)); out->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE( conv_data.next_texture[aiTextureType_DIFFUSE]++)); @@ -544,8 +493,9 @@ void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) { if (index == static_cast(-1)) { // Setup a default material. std::shared_ptr p(new Material()); - ai_assert(::strlen(AI_DEFAULT_MATERIAL_NAME) < sizeof(p->id.name) - 2); - strcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME); + const size_t len = ::strlen(AI_DEFAULT_MATERIAL_NAME); + ai_assert(len < sizeof(p->id.name) - 2); + memcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME, len); // Note: MSVC11 does not zero-initialize Material here, although it should. // Thus all relevant fields should be explicitly initialized. We cannot add @@ -1337,4 +1287,55 @@ aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, Convers return node.release(); } +BlenderImporter::StreamOrError BlenderImporter::ParseMagicToken(const std::string &pFile, IOSystem *pIOHandler) const { + std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); + if (stream == nullptr) { + return {{}, {}, "Could not open file for reading"}; + } + + char magic[8] = { 0 }; + stream->Read(magic, 7, 1); + if (strcmp(magic, Token) == 0) { + return {stream, {}, {}}; + } + + // Check for presence of the gzip header. If yes, assume it is a + // compressed blend file and try uncompressing it, else fail. This is to + // avoid uncompressing random files which our loader might end up with. +#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND + return {{}, {}, "BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"}; +#else + if (magic[0] != 0x1f || static_cast(magic[1]) != 0x8b) { + return {{}, {}, "BLENDER magic bytes are missing, couldn't find GZIP header either"}; + } + + LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file"); + if (magic[2] != 8) { + return {{}, {}, "Unsupported GZIP compression method"}; + } + + // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer + stream->Seek(0L, aiOrigin_SET); + std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); + + size_t total = 0; + Compression compression; + auto uncompressed = std::make_shared>(); + if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) { + total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), *uncompressed); + compression.close(); + } + + // replace the input stream with a memory stream + stream = std::make_shared(reinterpret_cast(uncompressed->data()), total); + + // .. and retry + stream->Read(magic, 7, 1); + if (strcmp(magic, Token) == 0) { + return {stream, uncompressed, {}}; + } + return {{}, {}, "Found no BLENDER magic word in decompressed GZIP file"}; +#endif +} + #endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.h index b29ee5941..5c800c627 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -180,6 +180,19 @@ private: const Blender::MTex *tex, Blender::ConversionData &conv_data); + // TODO: Move to a std::variant, once c++17 is supported. + struct StreamOrError { + std::shared_ptr stream; + std::shared_ptr> input; + std::string error; + }; + + // Returns either a stream (and optional input data for the stream) or + // an error if it can't parse the magic token. + StreamOrError ParseMagicToken( + const std::string &pFile, + IOSystem *pIOHandler) const; + private: // static stuff, mostly logging and error reporting. // -------------------- static void CheckActualType(const Blender::ElemBase *dt, diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.cpp index 6cc11ec8e..2cd8bda7c 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -283,6 +283,11 @@ void BlenderModifier_Subdivision ::DoIt(aiNode &out, ConversionData &conv_data, if (conv_data.meshes->empty()) { return; } + const size_t meshIndex = conv_data.meshes->size() - out.mNumMeshes; + if (meshIndex >= conv_data.meshes->size()) { + ASSIMP_LOG_ERROR("Invalid index detected."); + return; + } aiMesh **const meshes = &conv_data.meshes[conv_data.meshes->size() - out.mNumMeshes]; std::unique_ptr tempmeshes(new aiMesh *[out.mNumMeshes]()); diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.h index 5af560caf..2d6940357 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderModifier.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,9 +62,7 @@ public: /** * The class destructor, virtual. */ - virtual ~BlenderModifier() { - // empty - } + virtual ~BlenderModifier() = default; // -------------------- /** diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.cpp index 3a9a02fd0..e25dc450b 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.cpp @@ -102,10 +102,6 @@ void Structure::Convert( ReadFieldPtr(dest.next, "*next", db); { - //std::shared_ptr prev; - //ReadFieldPtr(prev, "*prev", db); - //dest.prev = prev.get(); - std::shared_ptr ob; ReadFieldPtr(ob, "*ob", db); dest.ob = ob.get(); @@ -301,7 +297,7 @@ void Structure ::Convert( const FileDatabase &db) const { // note: as per https://github.com/assimp/assimp/issues/128, // reading the Object linked list recursively is prone to stack overflow. - // This structure converter is therefore an hand-written exception that + // This structure converter is therefore a hand-written exception that // does it iteratively. const int initial_pos = db.reader->GetCurrentPos(); @@ -569,7 +565,7 @@ void Structure ::Convert( const FileDatabase &db) const { ReadFieldArray(dest.co, "co", db); - ReadFieldArray(dest.no, "no", db); + ReadFieldArray(dest.no, "no", db); ReadField(dest.flag, "flag", db); //ReadField(dest.mat_nr,"mat_nr",db); ReadField(dest.bweight, "bweight", db); diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.h index 436e47061..671891c68 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderScene.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -182,7 +182,7 @@ struct MVert : ElemBase { int bweight; MVert() : - ElemBase(), flag(0), mat_nr(0), bweight(0) {} + flag(0), mat_nr(0), bweight(0) {} }; // ------------------------------------------------------------------------------- @@ -417,7 +417,6 @@ struct CustomDataLayer : ElemBase { std::shared_ptr data; // must be converted to real type according type member CustomDataLayer() : - ElemBase(), type(0), offset(0), flag(0), @@ -729,7 +728,7 @@ struct Object : ElemBase { ListBase modifiers; Object() : - ElemBase(), type(Type_EMPTY), parent(nullptr), track(), proxy(), proxy_from(), data() { + type(Type_EMPTY), parent(nullptr) { // empty } }; @@ -741,8 +740,7 @@ struct Base : ElemBase { std::shared_ptr object WARN; Base() : - ElemBase(), prev(nullptr), next(), object() { - // empty + prev(nullptr) { // empty } }; @@ -758,10 +756,7 @@ struct Scene : ElemBase { ListBase base; - Scene() : - ElemBase(), camera(), world(), basact(), master_collection() { - // empty - } + Scene() = default; }; // ------------------------------------------------------------------------------- @@ -791,10 +786,7 @@ struct Image : ElemBase { short gen_x, gen_y, gen_type; - Image() : - ElemBase() { - // empty - } + Image() = default; }; // ------------------------------------------------------------------------------- @@ -884,7 +876,7 @@ struct Tex : ElemBase { //char use_nodes; Tex() : - ElemBase(), imaflag(ImageFlags_INTERPOL), type(Type_CLOUDS), ima() { + imaflag(ImageFlags_INTERPOL), type(Type_CLOUDS) { // empty } }; @@ -976,10 +968,7 @@ struct MTex : ElemBase { //float shadowfac; //float zenupfac, zendownfac, blendfac; - MTex() : - ElemBase() { - // empty - } + MTex() = default; }; } // namespace Blender diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.cpp b/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.cpp index d3ef5ae5e..f51cf9780 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.cpp +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,10 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file BlenderTessellator.cpp - * @brief A simple tessellation wrapper - */ - +/// @file BlenderTessellator.cpp +/// @brief A simple tessellation wrapper #ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER @@ -62,8 +59,7 @@ namspace Assimp { template< > const char* LogFunctions< BlenderTessellatorGL >::Prefix() { - static auto prefix = "BLEND_TESS_GL: "; - return prefix; + return "BLEND_TESS_GL: "; } } @@ -81,9 +77,7 @@ BlenderTessellatorGL::BlenderTessellatorGL( BlenderBMeshConverter& converter ): } // ------------------------------------------------------------------------------------------------ -BlenderTessellatorGL::~BlenderTessellatorGL( ) -{ -} +BlenderTessellatorGL::~BlenderTessellatorGL() = default; // ------------------------------------------------------------------------------------------------ void BlenderTessellatorGL::Tessellate( const MLoop* polyLoop, int vertexCount, const std::vector< MVert >& vertices ) @@ -259,8 +253,7 @@ namespace Assimp { template< > const char* LogFunctions< BlenderTessellatorP2T >::Prefix() { - static auto prefix = "BLEND_TESS_P2T: "; - return prefix; + return "BLEND_TESS_P2T: "; } } diff --git a/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.h b/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.h index 87703f99d..e43535f6c 100644 --- a/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.h +++ b/Engine/lib/assimp/code/AssetLib/Blender/BlenderTessellator.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -144,11 +143,7 @@ namespace Assimp #if ASSIMP_BLEND_WITH_POLY_2_TRI -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include "../contrib/poly2tri/poly2tri/poly2tri.h" -#endif +#include "contrib/poly2tri/poly2tri/poly2tri.h" namespace Assimp { diff --git a/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.cpp b/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.cpp index 06d7a3412..daef6ebe4 100644 --- a/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.cpp @@ -46,10 +46,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // no #ifdefing here, Cinema4D support is carried out in a branch of assimp // where it is turned on in the CMake settings. -#ifndef _MSC_VER -# error C4D support is currently MSVC only -#endif - #include "C4DImporter.h" #include #include @@ -86,8 +82,7 @@ void GetWriterInfo(int &id, String &appname) { namespace Assimp { template<> const char* LogFunctions::Prefix() { - static auto prefix = "C4D: "; - return prefix; + return "C4D: "; } } @@ -106,25 +101,20 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ -C4DImporter::C4DImporter() -: BaseImporter() { - // empty -} +C4DImporter::C4DImporter() = default; // ------------------------------------------------------------------------------------------------ -C4DImporter::~C4DImporter() { - // empty -} +C4DImporter::~C4DImporter() = default; // ------------------------------------------------------------------------------------------------ -bool C4DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const { +bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { const std::string& extension = GetExtension(pFile); if (extension == "c4d") { return true; } else if ((!extension.length() || checkSig) && pIOHandler) { // TODO } - + return false; } @@ -311,7 +301,7 @@ void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent) { // based on Cineware sample code while (object) { - const LONG type = object->GetType(); + const Int32 type = object->GetType(); const Matrix& ml = object->GetMl(); aiNode* const nd = new aiNode(); @@ -374,8 +364,8 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object) { PolygonObject* const polyObject = dynamic_cast(object); ai_assert(polyObject != nullptr); - const LONG pointCount = polyObject->GetPointCount(); - const LONG polyCount = polyObject->GetPolygonCount(); + const Int32 pointCount = polyObject->GetPointCount(); + const Int32 polyCount = polyObject->GetPolygonCount(); if(!polyObject || !pointCount) { LogWarn("ignoring mesh with zero vertices or faces"); return nullptr; @@ -397,7 +387,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object) { unsigned int vcount = 0; // first count vertices - for (LONG i = 0; i < polyCount; i++) + for (Int32 i = 0; i < polyCount; i++) { vcount += 3; @@ -440,7 +430,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object) { } // copy vertices and extra channels over and populate faces - for (LONG i = 0; i < polyCount; ++i, ++face) { + for (Int32 i = 0; i < polyCount; ++i, ++face) { ai_assert(polys[i].a < pointCount && polys[i].a >= 0); const Vector& pointA = points[polys[i].a]; verts->x = pointA.x; @@ -517,7 +507,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object) { if (tangents_src) { for(unsigned int k = 0; k < face->mNumIndices; ++k) { - LONG l; + Int32 l; switch(k) { case 0: l = polys[i].a; diff --git a/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.h b/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.h index c44cf5e37..effd2af09 100644 --- a/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.h +++ b/Engine/lib/assimp/code/AssetLib/C4D/C4DImporter.h @@ -78,6 +78,8 @@ namespace Assimp { // ------------------------------------------------------------------------------------------- class C4DImporter : public BaseImporter, public LogFunctions { public: + C4DImporter(); + ~C4DImporter() override; bool CanRead( const std::string& pFile, IOSystem*, bool checkSig) const override; protected: diff --git a/Engine/lib/assimp/code/AssetLib/COB/COBLoader.cpp b/Engine/lib/assimp/code/AssetLib/COB/COBLoader.cpp index a540ffaf1..6c85edfc9 100644 --- a/Engine/lib/assimp/code/AssetLib/COB/COBLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/COB/COBLoader.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -61,11 +61,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -using namespace Assimp; +namespace Assimp { using namespace Assimp::COB; using namespace Assimp::Formatter; -static const float units[] = { +static constexpr float units[] = { 1000.f, 100.f, 1.f, @@ -76,7 +76,7 @@ static const float units[] = { 1.f / 1609.344f }; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "TrueSpace Object Importer", "", "", @@ -89,14 +89,6 @@ static const aiImporterDesc desc = { "cob scn" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -COBImporter::COBImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -COBImporter::~COBImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool COBImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -158,7 +150,7 @@ void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // sort faces by material indices for (std::shared_ptr &n : scene.nodes) { if (n->type == Node::TYPE_MESH) { - Mesh &mesh = (Mesh &)(*n.get()); + Mesh &mesh = (Mesh &)(*n); for (Face &f : mesh.faces) { mesh.temp_map[f.material].push_back(&f); } @@ -168,7 +160,7 @@ void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // count meshes for (std::shared_ptr &n : scene.nodes) { if (n->type == Node::TYPE_MESH) { - Mesh &mesh = (Mesh &)(*n.get()); + Mesh &mesh = (Mesh &)(*n); if (mesh.vertex_positions.size() && mesh.texture_coords.size()) { pScene->mNumMeshes += static_cast(mesh.temp_map.size()); } @@ -211,7 +203,7 @@ void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } } - pScene->mRootNode = BuildNodes(*root.get(), scene, pScene); + pScene->mRootNode = BuildNodes(*root, scene, pScene); //flip normals after import FlipWindingOrderProcess flip; flip.Execute(pScene); @@ -380,9 +372,11 @@ aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fi } // add children recursively - nd->mChildren = new aiNode *[root.temp_children.size()](); - for (const Node *n : root.temp_children) { - (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd; + if (!root.temp_children.empty()) { + nd->mChildren = new aiNode *[root.temp_children.size()](); + for (const Node *n : root.temp_children) { + (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd; + } } return nd; @@ -481,8 +475,9 @@ void COBImporter::ReadBasicNodeInfo_Ascii(Node &msh, LineSplitter &splitter, con } else if (splitter.match_start("Transform")) { for (unsigned int y = 0; y < 4 && ++splitter; ++y) { const char *s = splitter->c_str(); + const char *end = s + splitter->size(); for (unsigned int x = 0; x < 4; ++x) { - SkipSpaces(&s); + SkipSpaces(&s, end); msh.transform[y][x] = fast_atof(&s); } } @@ -494,12 +489,12 @@ void COBImporter::ReadBasicNodeInfo_Ascii(Node &msh, LineSplitter &splitter, con // ------------------------------------------------------------------------------------------------ template -void COBImporter::ReadFloat3Tuple_Ascii(T &fill, const char **in) { +void COBImporter::ReadFloat3Tuple_Ascii(T &fill, const char **in, const char *end) { const char *rgb = *in; for (unsigned int i = 0; i < 3; ++i) { - SkipSpaces(&rgb); + SkipSpaces(&rgb, end); if (*rgb == ',') ++rgb; - SkipSpaces(&rgb); + SkipSpaces(&rgb, end); fill[i] = fast_atof(&rgb); } @@ -546,7 +541,7 @@ void COBImporter::ReadMat1_Ascii(Scene &out, LineSplitter &splitter, const Chunk } const char *rgb = splitter[1]; - ReadFloat3Tuple_Ascii(mat.rgb, &rgb); + ReadFloat3Tuple_Ascii(mat.rgb, &rgb, splitter.getEnd()); ++splitter; if (!splitter.match_start("alpha ")) { @@ -625,20 +620,21 @@ void COBImporter::ReadLght_Ascii(Scene &out, LineSplitter &splitter, const Chunk } const char *rgb = splitter[1]; - ReadFloat3Tuple_Ascii(msh.color, &rgb); + const char *end = splitter.getEnd(); + ReadFloat3Tuple_Ascii(msh.color, &rgb, end); - SkipSpaces(&rgb); + SkipSpaces(&rgb, end); if (strncmp(rgb, "cone angle", 10) != 0) { ASSIMP_LOG_WARN("Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id); } - SkipSpaces(rgb + 10, &rgb); + SkipSpaces(rgb + 10, &rgb, end); msh.angle = fast_atof(&rgb); - SkipSpaces(&rgb); + SkipSpaces(&rgb, end); if (strncmp(rgb, "inner angle", 11) != 0) { ASSIMP_LOG_WARN("Expected `inner angle` entity in `color` line in `Lght` chunk ", nfo.id); } - SkipSpaces(rgb + 11, &rgb); + SkipSpaces(rgb + 11, &rgb, end); msh.inner_angle = fast_atof(&rgb); // skip the rest for we can't handle this kind of physically-based lighting information. @@ -711,14 +707,14 @@ void COBImporter::ReadPolH_Ascii(Scene &out, LineSplitter &splitter, const Chunk for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { const char *s = splitter->c_str(); - + const char *end = splitter.getEnd(); aiVector3D &v = msh.vertex_positions[cur]; - SkipSpaces(&s); + SkipSpaces(&s, end); v.x = fast_atof(&s); - SkipSpaces(&s); + SkipSpaces(&s, end); v.y = fast_atof(&s); - SkipSpaces(&s); + SkipSpaces(&s, end); v.z = fast_atof(&s); } } else if (splitter.match_start("Texture Vertices")) { @@ -727,12 +723,13 @@ void COBImporter::ReadPolH_Ascii(Scene &out, LineSplitter &splitter, const Chunk for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { const char *s = splitter->c_str(); + const char *end = splitter.getEnd(); aiVector2D &v = msh.texture_coords[cur]; - SkipSpaces(&s); + SkipSpaces(&s, end); v.x = fast_atof(&s); - SkipSpaces(&s); + SkipSpaces(&s, end); v.y = fast_atof(&s); } } else if (splitter.match_start("Faces")) { @@ -757,8 +754,9 @@ void COBImporter::ReadPolH_Ascii(Scene &out, LineSplitter &splitter, const Chunk face.material = strtoul10(splitter[6]); const char *s = (++splitter)->c_str(); + const char *end = splitter.getEnd(); for (size_t i = 0; i < face.indices.size(); ++i) { - if (!SkipSpaces(&s)) { + if (!SkipSpaces(&s, end)) { ThrowException("Expected EOL token in Face entry"); } if ('<' != *s++) { @@ -868,7 +866,7 @@ void COBImporter::ReadBinaryFile(Scene &out, StreamReaderLE *reader) { return; } - while (1) { + while (true) { std::string type; type += reader->GetI1(); type += reader->GetI1(); @@ -1054,7 +1052,7 @@ void COBImporter::ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const id[0] = reader.GetI1(), id[1] = reader.GetI1(); if (id[0] == 'e' && id[1] == ':') { - mat.tex_env.reset(new Texture()); + mat.tex_env = std::make_shared(); reader.GetI1(); ReadString_Binary(mat.tex_env->path, reader); @@ -1064,7 +1062,7 @@ void COBImporter::ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const } if (id[0] == 't' && id[1] == ':') { - mat.tex_color.reset(new Texture()); + mat.tex_color = std::make_shared(); reader.GetI1(); ReadString_Binary(mat.tex_color->path, reader); @@ -1080,7 +1078,7 @@ void COBImporter::ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const } if (id[0] == 'b' && id[1] == ':') { - mat.tex_bump.reset(new Texture()); + mat.tex_bump = std::make_shared(); reader.GetI1(); ReadString_Binary(mat.tex_bump->path, reader); @@ -1172,4 +1170,6 @@ void COBImporter::ReadUnit_Binary(COB::Scene &out, StreamReaderLE &reader, const ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist"); } +} + #endif // ASSIMP_BUILD_NO_COB_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/COB/COBLoader.h b/Engine/lib/assimp/code/AssetLib/COB/COBLoader.h index e6eb96dc1..ec3c7756b 100644 --- a/Engine/lib/assimp/code/AssetLib/COB/COBLoader.h +++ b/Engine/lib/assimp/code/AssetLib/COB/COBLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,16 +56,16 @@ class LineSplitter; // TinyFormatter.h namespace Formatter { -template -class basic_formatter; -typedef class basic_formatter, std::allocator> format; + template + class basic_formatter; + typedef class basic_formatter, std::allocator> format; } // namespace Formatter // COBScene.h namespace COB { -struct ChunkInfo; -struct Node; -struct Scene; + struct ChunkInfo; + struct Node; + struct Scene; } // namespace COB // ------------------------------------------------------------------------------------------- @@ -75,8 +75,8 @@ struct Scene; // ------------------------------------------------------------------------------------------- class COBImporter : public BaseImporter { public: - COBImporter(); - ~COBImporter() override; + COBImporter() = default; + ~COBImporter() override = default; // -------------------- bool CanRead(const std::string &pFile, IOSystem *pIOHandler, @@ -120,7 +120,7 @@ private: void ReadChunkInfo_Ascii(COB::ChunkInfo &out, const LineSplitter &splitter); void ReadBasicNodeInfo_Ascii(COB::Node &msh, LineSplitter &splitter, const COB::ChunkInfo &nfo); template - void ReadFloat3Tuple_Ascii(T &fill, const char **in); + void ReadFloat3Tuple_Ascii(T &fill, const char **in, const char *end); void ReadPolH_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); void ReadBitM_Ascii(COB::Scene &out, LineSplitter &splitter, const COB::ChunkInfo &nfo); diff --git a/Engine/lib/assimp/code/AssetLib/COB/COBScene.h b/Engine/lib/assimp/code/AssetLib/COB/COBScene.h index cc49de276..ea4c01251 100644 --- a/Engine/lib/assimp/code/AssetLib/COB/COBScene.h +++ b/Engine/lib/assimp/code/AssetLib/COB/COBScene.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.cpp b/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.cpp index 7bf736298..47beee514 100644 --- a/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -44,9 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file CSMLoader.cpp * Implementation of the CSM importer class. */ - - - #ifndef ASSIMP_BUILD_NO_CSM_IMPORTER #include "CSMLoader.h" @@ -63,7 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "CharacterStudio Motion Importer (MoCap)", "", "", @@ -79,33 +76,26 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -CSMImporter::CSMImporter() -: noSkeletonMesh() -{} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -CSMImporter::~CSMImporter() = default; +CSMImporter::CSMImporter() : noSkeletonMesh(){ + // empty +} // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const -{ +bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const { static const char* tokens[] = {"$Filename"}; return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens)); } // ------------------------------------------------------------------------------------------------ // Build a string of all file extensions supported -const aiImporterDesc* CSMImporter::GetInfo () const -{ +const aiImporterDesc* CSMImporter::GetInfo () const { return &desc; } // ------------------------------------------------------------------------------------------------ // Setup configuration properties for the loader -void CSMImporter::SetupProperties(const Importer* pImp) -{ +void CSMImporter::SetupProperties(const Importer* pImp) { noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; } @@ -117,7 +107,7 @@ void CSMImporter::InternReadFile( const std::string& pFile, std::unique_ptr file( pIOHandler->Open( pFile, "rb")); // Check whether we can read from the file - if( file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError( "Failed to open CSM file ", pFile, "."); } @@ -125,38 +115,38 @@ void CSMImporter::InternReadFile( const std::string& pFile, std::vector mBuffer2; TextFileToBuffer(file.get(),mBuffer2); const char* buffer = &mBuffer2[0]; - + const char *end = &mBuffer2[mBuffer2.size() - 1] + 1; std::unique_ptr anim(new aiAnimation()); int first = 0, last = 0x00ffffff; // now process the file and look out for '$' sections - while (1) { - SkipSpaces(&buffer); + while (true) { + SkipSpaces(&buffer, end); if ('\0' == *buffer) break; if ('$' == *buffer) { ++buffer; if (TokenMatchI(buffer,"firstframe",10)) { - SkipSpaces(&buffer); + SkipSpaces(&buffer, end); first = strtol10(buffer,&buffer); } else if (TokenMatchI(buffer,"lastframe",9)) { - SkipSpaces(&buffer); + SkipSpaces(&buffer, end); last = strtol10(buffer,&buffer); } else if (TokenMatchI(buffer,"rate",4)) { - SkipSpaces(&buffer); - float d; + SkipSpaces(&buffer, end); + float d = { 0.0f }; buffer = fast_atoreal_move(buffer,d); anim->mTicksPerSecond = d; } else if (TokenMatchI(buffer,"order",5)) { std::vector< aiNodeAnim* > anims_temp; anims_temp.reserve(30); - while (1) { - SkipSpaces(&buffer); - if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer) && *buffer == '$') + while (true) { + SkipSpaces(&buffer, end); + if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer, end) && *buffer == '$') break; // next section // Construct a new node animation channel and setup its name @@ -164,41 +154,43 @@ void CSMImporter::InternReadFile( const std::string& pFile, aiNodeAnim* nda = anims_temp.back(); char* ot = nda->mNodeName.data; - while (!IsSpaceOrNewLine(*buffer)) + while (!IsSpaceOrNewLine(*buffer)) { *ot++ = *buffer++; + } *ot = '\0'; nda->mNodeName.length = static_cast(ot-nda->mNodeName.data); } anim->mNumChannels = static_cast(anims_temp.size()); - if (!anim->mNumChannels) + if (!anim->mNumChannels) { throw DeadlyImportError("CSM: Empty $order section"); + } // copy over to the output animation anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; ::memcpy(anim->mChannels,&anims_temp[0],sizeof(aiNodeAnim*)*anim->mNumChannels); - } - else if (TokenMatchI(buffer,"points",6)) { - if (!anim->mNumChannels) + } else if (TokenMatchI(buffer,"points",6)) { + if (!anim->mNumChannels) { throw DeadlyImportError("CSM: \'$order\' section is required to appear prior to \'$points\'"); + } // If we know how many frames we'll read, we can preallocate some storage unsigned int alloc = 100; - if (last != 0x00ffffff) - { + if (last != 0x00ffffff) { alloc = last-first; alloc += alloc>>2u; // + 25% - for (unsigned int i = 0; i < anim->mNumChannels;++i) + for (unsigned int i = 0; i < anim->mNumChannels; ++i) { anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc]; + } } unsigned int filled = 0; // Now read all point data. - while (1) { - SkipSpaces(&buffer); - if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer) || *buffer == '$')) { + while (true) { + SkipSpaces(&buffer, end); + if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer, end) || *buffer == '$')) { break; // next section } @@ -209,8 +201,8 @@ void CSMImporter::InternReadFile( const std::string& pFile, for (unsigned int i = 0; i < anim->mNumChannels;++i) { aiNodeAnim* s = anim->mChannels[i]; - if (s->mNumPositionKeys == alloc) { /* need to reallocate? */ - + if (s->mNumPositionKeys == alloc) { + // need to reallocate? aiVectorKey* old = s->mPositionKeys; s->mPositionKeys = new aiVectorKey[s->mNumPositionKeys = alloc*2]; ::memcpy(s->mPositionKeys,old,sizeof(aiVectorKey)*alloc); @@ -218,24 +210,26 @@ void CSMImporter::InternReadFile( const std::string& pFile, } // read x,y,z - if(!SkipSpacesAndLineEnd(&buffer)) + if (!SkipSpacesAndLineEnd(&buffer, end)) { throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample x coord"); + } if (TokenMatchI(buffer, "DROPOUT", 7)) { // seems this is invalid marker data; at least the doc says it's possible ASSIMP_LOG_WARN("CSM: Encountered invalid marker data (DROPOUT)"); - } - else { + } else { aiVectorKey* sub = s->mPositionKeys + s->mNumPositionKeys; sub->mTime = (double)frame; buffer = fast_atoreal_move(buffer, (float&)sub->mValue.x); - if(!SkipSpacesAndLineEnd(&buffer)) + if (!SkipSpacesAndLineEnd(&buffer, end)) { throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample y coord"); + } buffer = fast_atoreal_move(buffer, (float&)sub->mValue.y); - if(!SkipSpacesAndLineEnd(&buffer)) + if (!SkipSpacesAndLineEnd(&buffer, end)) { throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample z coord"); + } buffer = fast_atoreal_move(buffer, (float&)sub->mValue.z); ++s->mNumPositionKeys; @@ -243,22 +237,22 @@ void CSMImporter::InternReadFile( const std::string& pFile, } // update allocation granularity - if (filled == alloc) + if (filled == alloc) { alloc *= 2; + } ++filled; } // all channels must be complete in order to continue safely. for (unsigned int i = 0; i < anim->mNumChannels;++i) { - - if (!anim->mChannels[i]->mNumPositionKeys) + if (!anim->mChannels[i]->mNumPositionKeys) { throw DeadlyImportError("CSM: Invalid marker track"); + } } } - } - else { + } else { // advance to the next line - SkipLine(&buffer); + SkipLine(&buffer, end); } } @@ -272,7 +266,7 @@ void CSMImporter::InternReadFile( const std::string& pFile, pScene->mRootNode->mNumChildren = anim->mNumChannels; pScene->mRootNode->mChildren = new aiNode* [anim->mNumChannels]; - for (unsigned int i = 0; i < anim->mNumChannels;++i) { + for (unsigned int i = 0; i < anim->mNumChannels;++i) { aiNodeAnim* na = anim->mChannels[i]; aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode(); diff --git a/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.h b/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.h index e9c4cd5ee..2bad73717 100644 --- a/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.h +++ b/Engine/lib/assimp/code/AssetLib/CSM/CSMLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -61,7 +61,7 @@ namespace Assimp { class CSMImporter : public BaseImporter { public: CSMImporter(); - ~CSMImporter() override; + ~CSMImporter() override = default; // ------------------------------------------------------------------- bool CanRead(const std::string &pFile, IOSystem *pIOHandler, @@ -81,9 +81,8 @@ protected: private: bool noSkeletonMesh; -}; // end of class CSMImporter +}; -} // end of namespace Assimp +} // namespace Assimp #endif // AI_AC3DIMPORTER_H_INC - diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.cpp b/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.cpp index 40f49e65f..3fc3a6e15 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -91,7 +91,7 @@ void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *p // Encodes a string into a valid XML ID using the xsd:ID schema qualifications. static const std::string XMLIDEncode(const std::string &name) { const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-."; - const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char); + const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char) - 1; if (name.length() == 0) { return name; @@ -246,7 +246,7 @@ void ColladaExporter::WriteHeader() { } // Assimp root nodes can have meshes, Collada Scenes cannot - if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != 0) { + if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != nullptr) { mAdd_root_node = true; } @@ -448,7 +448,7 @@ void ColladaExporter::WriteLight(size_t pIndex) { PushTag(); switch (light->mType) { case aiLightSource_AMBIENT: - WriteAmbienttLight(light); + WriteAmbientLight(light); break; case aiLightSource_DIRECTIONAL: WriteDirectionalLight(light); @@ -543,7 +543,7 @@ void ColladaExporter::WriteSpotLight(const aiLight *const light) { mOutput << startstr << "" << endstr; } -void ColladaExporter::WriteAmbienttLight(const aiLight *const light) { +void ColladaExporter::WriteAmbientLight(const aiLight *const light) { const aiColor3D &color = light->mColorAmbient; mOutput << startstr << "" << endstr; diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.h b/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.h index 7288dce54..05e076034 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -101,7 +101,7 @@ protected: void WritePointLight(const aiLight *const light); void WriteDirectionalLight(const aiLight *const light); void WriteSpotLight(const aiLight *const light); - void WriteAmbienttLight(const aiLight *const light); + void WriteAmbientLight(const aiLight *const light); /// Writes the controller library void WriteControllerLibrary(); diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.cpp b/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.cpp index 0fb172fbb..b5de70624 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.cpp +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.h b/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.h index 2930f5108..6662d7354 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.h +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaHelper.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -666,7 +666,7 @@ struct ChannelEntry { const Collada::Accessor *mTimeAccessor; ///> Collada accessor to the time values const Collada::Data *mTimeData; ///> Source data array for the time values const Collada::Accessor *mValueAccessor; ///> Collada accessor to the key value values - const Collada::Data *mValueData; ///> Source datat array for the key value values + const Collada::Data *mValueData; ///> Source data array for the key value values ChannelEntry() : mChannel(), diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.cpp b/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.cpp index 405944f29..6d7085b35 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -64,7 +64,7 @@ namespace Assimp { using namespace Assimp::Formatter; using namespace Assimp::Collada; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Collada Importer", "", "", @@ -92,27 +92,15 @@ inline void AddNodeMetaData(aiNode *node, const std::string &key, const T &value // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer ColladaLoader::ColladaLoader() : - mFileName(), - mMeshIndexByID(), - mMaterialIndexByName(), - mMeshes(), - newMats(), - mCameras(), - mLights(), - mTextures(), - mAnims(), noSkeletonMesh(false), removeEmptyBones(false), ignoreUpDirection(false), + ignoreUnitSize(false), useColladaName(false), mNodeNameCounter(0) { // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ColladaLoader::~ColladaLoader() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -131,6 +119,7 @@ void ColladaLoader::SetupProperties(const Importer *pImp) { noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; removeEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true) != 0; ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0; + ignoreUnitSize = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UNIT_SIZE, 0) != 0; useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0; } @@ -179,12 +168,15 @@ void ColladaLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IO // ... then fill the materials with the now adjusted settings FillMaterials(parser, pScene); - // Apply unit-size scale calculation - - pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0, - 0, parser.mUnitSize, 0, 0, - 0, 0, parser.mUnitSize, 0, - 0, 0, 0, 1); + if (!ignoreUnitSize) { + // Apply unit-size scale calculation + pScene->mRootNode->mTransformation *= aiMatrix4x4( + parser.mUnitSize, 0, 0, 0, + 0, parser.mUnitSize, 0, 0, + 0, 0, parser.mUnitSize, 0, + 0, 0, 0, 1); + } + if (!ignoreUpDirection) { // Convert to Y_UP, if different orientation if (parser.mUpDirection == ColladaParser::UP_X) { @@ -255,7 +247,9 @@ aiNode *ColladaLoader::BuildHierarchy(const ColladaParser &pParser, const Collad // add children. first the *real* ones node->mNumChildren = static_cast(pNode->mChildren.size() + instances.size()); - node->mChildren = new aiNode *[node->mNumChildren]; + if (node->mNumChildren != 0) { + node->mChildren = new aiNode * [node->mNumChildren]; + } for (size_t a = 0; a < pNode->mChildren.size(); ++a) { node->mChildren[a] = BuildHierarchy(pParser, pNode->mChildren[a]); @@ -631,16 +625,14 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrc } // same for texture coords, as many as we have - // empty slots are not allowed, need to pack and adjust UV indexes accordingly - for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { if (pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) { - dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; + dstMesh->mTextureCoords[a] = new aiVector3D[numVertices]; for (size_t b = 0; b < numVertices; ++b) { - dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; + dstMesh->mTextureCoords[a][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; } - dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; - ++real; + dstMesh->mNumUVComponents[a] = pSrcMesh->mNumUVComponents[a]; } } @@ -1264,12 +1256,12 @@ void ColladaLoader::CreateAnimation(aiScene *pScene, const ColladaParser &pParse // now for every unique point in time, find or interpolate the key values for that time // and apply them to the transform chain. Then the node's present transformation can be calculated. ai_real time = startTime; - while (1) { + while (true) { for (ChannelEntry & e : entries) { // find the keyframe behind the current point in time size_t pos = 0; ai_real postTime = 0.0; - while (1) { + while (true) { if (pos >= e.mTimeAccessor->mCount) { break; } @@ -1523,7 +1515,7 @@ void ColladaLoader::AddTexture(aiMaterial &mat, map = -1; for (std::string::const_iterator it = sampler.mUVChannel.begin(); it != sampler.mUVChannel.end(); ++it) { if (IsNumeric(*it)) { - map = strtoul10(&(*it)); + map = strtoul10(&(*it)); break; } } @@ -1680,7 +1672,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser &pParse // recurse through the param references until we end up at an image std::string name = pName; - while (1) { + while (true) { // the given string is a param entry. Find it Effect::ParamLibrary::const_iterator it = pEffect.mParams.find(name); // if not found, we're at the end of the recursion. The resulting string should be the image ID diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.h b/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.h index 870c12a5a..0603d419c 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaLoader.h @@ -4,7 +4,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -86,7 +86,7 @@ public: ColladaLoader(); /// The class destructor. - ~ColladaLoader() override; + ~ColladaLoader() override = default; /// Returns whether the class can handle the format of the given file. /// @see BaseImporter::CanRead() for more details. @@ -239,6 +239,7 @@ protected: bool noSkeletonMesh; bool removeEmptyBones; bool ignoreUpDirection; + bool ignoreUnitSize; bool useColladaName; /** Used by FindNameForNode() to generate unique node names */ diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.cpp b/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.cpp index fd2662ddb..0741b3c73 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include using namespace Assimp; using namespace Assimp::Collada; @@ -67,7 +68,7 @@ static void ReportWarning(const char *msg, ...) { va_start(args, msg); char szBuffer[3000]; - const int iLen = vsprintf(szBuffer, msg, args); + const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args); ai_assert(iLen > 0); va_end(args); @@ -144,7 +145,7 @@ ColladaParser::ColladaParser(IOSystem *pIOHandler, const std::string &pFile) : } else { // attempt to open the file directly daefile.reset(pIOHandler->Open(pFile)); - if (daefile.get() == nullptr) { + if (daefile == nullptr) { throw DeadlyImportError("Failed to open file '", pFile, "'."); } } @@ -634,7 +635,8 @@ void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controlle const std::string ¤tName = currentNode.name(); if (currentName == "morph") { controller.mType = Morph; - controller.mMeshId = currentNode.attribute("source").as_string(); + std::string id = currentNode.attribute("source").as_string(); + controller.mMeshId = id.substr(1, id.size() - 1); int methodIndex = currentNode.attribute("method").as_int(); if (methodIndex > 0) { std::string method; @@ -653,12 +655,13 @@ void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controlle std::string v; XmlParser::getValueAsString(currentNode, v); const char *content = v.c_str(); + const char *end = content + v.size(); for (unsigned int a = 0; a < 16; a++) { - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); // read a number content = fast_atoreal_move(content, controller.mBindShapeMatrix[a]); // skip whitespace after it - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } } else if (currentName == "source") { ReadSource(currentNode); @@ -739,7 +742,9 @@ void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pC throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in data element"); } } else if (currentName == "vcount" && vertexCount > 0) { - const char *text = currentNode.text().as_string(); + const std::string stdText = currentNode.text().as_string(); + const char *text = stdText.c_str(); + const char *end = text + stdText.size(); size_t numWeights = 0; for (std::vector::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) { if (*text == 0) { @@ -748,7 +753,7 @@ void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pC *it = strtoul10(text, &text); numWeights += *it; - SkipSpacesAndLineEnd(&text); + SkipSpacesAndLineEnd(&text, end); } // reserve weight count pController.mWeights.resize(numWeights); @@ -757,17 +762,19 @@ void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pC std::string stdText; XmlParser::getValueAsString(currentNode, stdText); const char *text = stdText.c_str(); + const char *end = text + stdText.size(); for (std::vector>::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) { - if (text == 0) { + if (text == nullptr) { throw DeadlyImportError("Out of data while reading "); } + SkipSpacesAndLineEnd(&text, end); it->first = strtoul10(text, &text); - SkipSpacesAndLineEnd(&text); + SkipSpacesAndLineEnd(&text, end); if (*text == 0) { throw DeadlyImportError("Out of data while reading "); } it->second = strtoul10(text, &text); - SkipSpacesAndLineEnd(&text); + SkipSpacesAndLineEnd(&text, end); } } } @@ -812,38 +819,38 @@ void ColladaParser::ReadImage(XmlNode &node, Collada::Image &pImage) { if (!pImage.mFileName.length()) { pImage.mFileName = "unknown_texture"; } - } - } else if (mFormat == FV_1_5_n) { - std::string value; - XmlNode refChild = currentNode.child("ref"); - XmlNode hexChild = currentNode.child("hex"); - if (refChild) { - // element content is filename - hopefully - if (XmlParser::getValueAsString(refChild, value)) { - aiString filepath(value); - UriDecodePath(filepath); - pImage.mFileName = filepath.C_Str(); - } - } else if (hexChild && !pImage.mFileName.length()) { - // embedded image. get format - pImage.mEmbeddedFormat = hexChild.attribute("format").as_string(); - if (pImage.mEmbeddedFormat.empty()) { - ASSIMP_LOG_WARN("Collada: Unknown image file format"); - } + } else if (mFormat == FV_1_5_n) { + std::string value; + XmlNode refChild = currentNode.child("ref"); + XmlNode hexChild = currentNode.child("hex"); + if (refChild) { + // element content is filename - hopefully + if (XmlParser::getValueAsString(refChild, value)) { + aiString filepath(value); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } + } else if (hexChild && !pImage.mFileName.length()) { + // embedded image. get format + pImage.mEmbeddedFormat = hexChild.attribute("format").as_string(); + if (pImage.mEmbeddedFormat.empty()) { + ASSIMP_LOG_WARN("Collada: Unknown image file format"); + } - XmlParser::getValueAsString(hexChild, value); - const char *data = value.c_str(); - // hexadecimal-encoded binary octets. First of all, find the - // required buffer size to reserve enough storage. - const char *cur = data; - while (!IsSpaceOrNewLine(*cur)) { - ++cur; - } + XmlParser::getValueAsString(hexChild, value); + const char *data = value.c_str(); + // hexadecimal-encoded binary octets. First of all, find the + // required buffer size to reserve enough storage. + const char *cur = data; + while (!IsSpaceOrNewLine(*cur)) { + ++cur; + } - const unsigned int size = (unsigned int)(cur - data) * 2; - pImage.mImageData.resize(size); - for (unsigned int i = 0; i < size; ++i) { - pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1)); + const unsigned int size = (unsigned int)(cur - data) * 2; + pImage.mImageData.resize(size); + for (unsigned int i = 0; i < size; ++i) { + pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1)); + } } } } @@ -950,44 +957,45 @@ void ColladaParser::ReadLight(XmlNode &node, Collada::Light &pLight) { std::string v; XmlParser::getValueAsString(currentNode, v); const char *content = v.c_str(); + const char *end = content + v.size(); content = fast_atoreal_move(content, (ai_real &)pLight.mColor.r); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); content = fast_atoreal_move(content, (ai_real &)pLight.mColor.g); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); content = fast_atoreal_move(content, (ai_real &)pLight.mColor.b); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } else if (currentName == "constant_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttConstant); + XmlParser::getValueAsReal(currentNode, pLight.mAttConstant); } else if (currentName == "linear_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttLinear); + XmlParser::getValueAsReal(currentNode, pLight.mAttLinear); } else if (currentName == "quadratic_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttQuadratic); + XmlParser::getValueAsReal(currentNode, pLight.mAttQuadratic); } else if (currentName == "falloff_angle") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffAngle); } else if (currentName == "falloff_exponent") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffExponent); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffExponent); } // FCOLLADA extensions // ------------------------------------------------------- else if (currentName == "outer_cone") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } else if (currentName == "penumbra_angle") { // this one is deprecated, now calculated using outer_cone - XmlParser::getValueAsFloat(currentNode, pLight.mPenumbraAngle); + XmlParser::getValueAsReal(currentNode, pLight.mPenumbraAngle); } else if (currentName == "intensity") { - XmlParser::getValueAsFloat(currentNode, pLight.mIntensity); + XmlParser::getValueAsReal(currentNode, pLight.mIntensity); } else if (currentName == "falloff") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } else if (currentName == "hotspot_beam") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffAngle); } // OpenCOLLADA extensions // ------------------------------------------------------- else if (currentName == "decay_falloff") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } } } @@ -1002,15 +1010,15 @@ void ColladaParser::ReadCamera(XmlNode &node, Collada::Camera &camera) { if (currentName == "orthographic") { camera.mOrtho = true; } else if (currentName == "xfov" || currentName == "xmag") { - XmlParser::getValueAsFloat(currentNode, camera.mHorFov); + XmlParser::getValueAsReal(currentNode, camera.mHorFov); } else if (currentName == "yfov" || currentName == "ymag") { - XmlParser::getValueAsFloat(currentNode, camera.mVerFov); + XmlParser::getValueAsReal(currentNode, camera.mVerFov); } else if (currentName == "aspect_ratio") { - XmlParser::getValueAsFloat(currentNode, camera.mAspect); + XmlParser::getValueAsReal(currentNode, camera.mAspect); } else if (currentName == "znear") { - XmlParser::getValueAsFloat(currentNode, camera.mZNear); + XmlParser::getValueAsReal(currentNode, camera.mZNear); } else if (currentName == "zfar") { - XmlParser::getValueAsFloat(currentNode, camera.mZFar); + XmlParser::getValueAsReal(currentNode, camera.mZFar); } } } @@ -1162,15 +1170,15 @@ void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) { } else if (currentName == "mirrorV") { XmlParser::getValueAsBool(currentNode, out.mMirrorV); } else if (currentName == "repeatU") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.x); + XmlParser::getValueAsReal(currentNode, out.mTransform.mScaling.x); } else if (currentName == "repeatV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.y); + XmlParser::getValueAsReal(currentNode, out.mTransform.mScaling.y); } else if (currentName == "offsetU") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.x); + XmlParser::getValueAsReal(currentNode, out.mTransform.mTranslation.x); } else if (currentName == "offsetV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.y); + XmlParser::getValueAsReal(currentNode, out.mTransform.mTranslation.y); } else if (currentName == "rotateUV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mRotation); + XmlParser::getValueAsReal(currentNode, out.mTransform.mRotation); } else if (currentName == "blend_mode") { std::string v; XmlParser::getValueAsString(currentNode, v); @@ -1190,14 +1198,14 @@ void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) { // OKINO extensions // ------------------------------------------------------- else if (currentName == "weighting") { - XmlParser::getValueAsFloat(currentNode, out.mWeighting); + XmlParser::getValueAsReal(currentNode, out.mWeighting); } else if (currentName == "mix_with_previous_layer") { - XmlParser::getValueAsFloat(currentNode, out.mMixWithPrevious); + XmlParser::getValueAsReal(currentNode, out.mMixWithPrevious); } // MAX3D extensions // ------------------------------------------------------- else if (currentName == "amount") { - XmlParser::getValueAsFloat(currentNode, out.mWeighting); + XmlParser::getValueAsReal(currentNode, out.mWeighting); } } } @@ -1218,18 +1226,19 @@ void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &p std::string v; XmlParser::getValueAsString(currentNode, v); const char *content = v.c_str(); + const char *end = v.c_str() + v.size() + 1; content = fast_atoreal_move(content, (ai_real &)pColor.r); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); content = fast_atoreal_move(content, (ai_real &)pColor.g); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); content = fast_atoreal_move(content, (ai_real &)pColor.b); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); content = fast_atoreal_move(content, (ai_real &)pColor.a); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } else if (currentName == "texture") { // get name of source texture/sampler XmlParser::getStdStrAttribute(currentNode, "texture", pSampler.mName); @@ -1256,13 +1265,13 @@ void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &p // ------------------------------------------------------------------------------------------------ // Reads an effect entry containing a float -void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pFloat) { - pFloat = 0.f; +void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pReal) { + pReal = 0.f; XmlNode floatNode = node.child("float"); if (floatNode.empty()) { return; } - XmlParser::getValueAsFloat(floatNode, pFloat); + XmlParser::getValueAsReal(floatNode, pReal); } // ------------------------------------------------------------------------------------------------ @@ -1272,9 +1281,7 @@ void ColladaParser::ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam) return; } - XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); - XmlNode currentNode; - while (xmlIt.getNext(currentNode)) { + for (XmlNode ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "surface") { // image ID given inside tags @@ -1287,22 +1294,24 @@ void ColladaParser::ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam) } } else if (currentName == "sampler2D" && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) { // surface ID is given inside tags - const char *content = currentNode.value(); - pParam.mType = Param_Sampler; - pParam.mReference = content; + XmlNode source = currentNode.child("source"); + if (source) { + std::string v; + XmlParser::getValueAsString(source, v); + pParam.mType = Param_Sampler; + pParam.mReference = v.c_str(); + } } else if (currentName == "sampler2D") { // surface ID is given inside tags - std::string url; - XmlParser::getStdStrAttribute(currentNode, "url", url); - if (url[0] != '#') { - throw DeadlyImportError("Unsupported URL format in instance_image"); - } - pParam.mType = Param_Sampler; - pParam.mReference = url.c_str() + 1; - } else if (currentName == "source") { - const char *source = currentNode.child_value(); - if (nullptr != source) { - pParam.mReference = source; + XmlNode instance_image = currentNode.child("instance_image"); + if (instance_image) { + std::string url; + XmlParser::getStdStrAttribute(instance_image, "url", url); + if (url[0] != '#') { + throw DeadlyImportError("Unsupported URL format in instance_image"); + } + pParam.mType = Param_Sampler; + pParam.mReference = url.c_str() + 1; } } } @@ -1343,6 +1352,7 @@ void ColladaParser::ReadGeometry(XmlNode &node, Collada::Mesh &pMesh) { if (node.empty()) { return; } + for (XmlNode ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "mesh") { @@ -1413,6 +1423,7 @@ void ColladaParser::ReadDataArray(XmlNode &node) { XmlParser::getValueAsString(node, v); v = ai_trim(v); const char *content = v.c_str(); + const char *end = content + v.size(); // read values and store inside an array in the data library mDataLibrary[id] = Data(); @@ -1431,11 +1442,13 @@ void ColladaParser::ReadDataArray(XmlNode &node) { } s.clear(); - while (!IsSpaceOrNewLine(*content)) - s += *content++; + while (!IsSpaceOrNewLine(*content)) { + s += *content; + content++; + } data.mStrings.push_back(s); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } } else { data.mValues.reserve(count); @@ -1450,7 +1463,7 @@ void ColladaParser::ReadDataArray(XmlNode &node) { content = fast_atoreal_move(content, value); data.mValues.push_back(value); // skip whitespace after it - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } } } @@ -1615,8 +1628,10 @@ void ColladaParser::ReadIndexData(XmlNode &node, Mesh &pMesh) { std::string v; XmlParser::getValueAsString(currentNode, v); const char *content = v.c_str(); + const char *end = content + v.size(); + vcount.reserve(numPrimitives); - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); for (unsigned int a = 0; a < numPrimitives; a++) { if (*content == 0) { throw DeadlyImportError("Expected more values while reading contents."); @@ -1624,7 +1639,7 @@ void ColladaParser::ReadIndexData(XmlNode &node, Mesh &pMesh) { // read a number vcount.push_back((size_t)strtoul10(content, &content)); // skip whitespace after it - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); } } } @@ -1733,14 +1748,16 @@ size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vectormData) { acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); + const size_t dataSize = acc->mOffset + acc->mCount * acc->mStride; + if (dataSize > acc->mData->mValues.size()) { + throw DeadlyImportError("Not enough data for accessor"); + } } } // and the same for the per-index channels @@ -1794,13 +1815,19 @@ size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vectormData) { acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); + const size_t dataSize = acc->mOffset + acc->mCount * acc->mStride; + if (dataSize > acc->mData->mValues.size()) { + throw DeadlyImportError("Not enough data for accessor"); + } } } // For continued primitives, the given count does not come all in one

, but only one primitive per

size_t numPrimitives = pNumPrimitives; - if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon) + if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon) { numPrimitives = 1; + } + // For continued primitives, the given count is actually the number of

's inside the parent tag if (pPrimType == Prim_TriStrips) { size_t numberOfVertices = indices.size() / numOffsets; @@ -1853,7 +1880,6 @@ size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector and tags this function will create wrong uv coordinates. -///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior +///It's not clear from COLLADA documentation whether this is allowed or not. For now only exporter fixed to avoid such behavior void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh &pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices) { // calculate the base offset of the vertex whose attributes we ant to copy @@ -2165,15 +2191,15 @@ void ColladaParser::ReadNodeTransformation(XmlNode &node, Node *pNode, Transform } // how many parameters to read per transformation type - static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 }; + static constexpr unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 }; std::string value; XmlParser::getValueAsString(node, value); const char *content = value.c_str(); - + const char *end = value.c_str() + value.size(); // read as many parameters and store in the transformation for (unsigned int a = 0; a < sNumParameters[pType]; a++) { // skip whitespace before the number - SkipSpacesAndLineEnd(&content); + SkipSpacesAndLineEnd(&content, end); // read a number content = fast_atoreal_move(content, tf.f[a]); } @@ -2267,9 +2293,9 @@ void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) { urlMat++; s.mMatName = urlMat; + ReadMaterialVertexInputBinding(instanceMatNode, s); // store the association instance.mMaterials[group] = s; - ReadMaterialVertexInputBinding(instanceMatNode, s); } } } @@ -2305,7 +2331,7 @@ void ColladaParser::ReadScene(XmlNode &node) { // find the referred scene, skip the leading # NodeLibrary::const_iterator sit = mNodeLibrary.find(url.c_str() + 1); if (sit == mNodeLibrary.end()) { - throw DeadlyImportError("Unable to resolve visual_scene reference \"", std::string(url), "\" in element."); + throw DeadlyImportError("Unable to resolve visual_scene reference \"", std::string(std::move(url)), "\" in element."); } mRootNode = sit->second; } diff --git a/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.h b/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.h index 15982934f..d428ad674 100644 --- a/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.h +++ b/Engine/lib/assimp/code/AssetLib/Collada/ColladaParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- - Copyright (c) 2006-2022, assimp team + Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/DXF/DXFHelper.h b/Engine/lib/assimp/code/AssetLib/DXF/DXFHelper.h index e50c471d2..0bbecdc9e 100644 --- a/Engine/lib/assimp/code/AssetLib/DXF/DXFHelper.h +++ b/Engine/lib/assimp/code/AssetLib/DXF/DXFHelper.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -65,7 +65,6 @@ public: LineReader(StreamReaderLE& reader) : splitter(reader,false,true) , groupcode( 0 ) - , value() , end() { // empty } @@ -186,8 +185,7 @@ struct InsertBlock { InsertBlock() : pos() , scale(1.f,1.f,1.f) - , angle() - , name() { + , angle() { // empty } diff --git a/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.cpp b/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.cpp index f0091f01e..0f3da2626 100644 --- a/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of the DXF importer class */ - #ifndef ASSIMP_BUILD_NO_DXF_IMPORTER #include "AssetLib/DXF/DXFLoader.h" @@ -57,6 +56,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include using namespace Assimp; @@ -67,26 +67,269 @@ static constexpr size_t AI_DXF_BINARY_IDENT_LEN = sizeof AI_DXF_BINARY_IDENT; // default vertex color that all uncolored vertices will receive static const aiColor4D AI_DXF_DEFAULT_COLOR(aiColor4D(0.6f, 0.6f, 0.6f, 0.6f)); -// color indices for DXF - 16 are supported, the table is -// taken directly from the DXF spec. -static aiColor4D g_aclrDxfIndexColors[] = { - aiColor4D (0.6f, 0.6f, 0.6f, 1.0f), - aiColor4D (1.0f, 0.0f, 0.0f, 1.0f), // red - aiColor4D (0.0f, 1.0f, 0.0f, 1.0f), // green - aiColor4D (0.0f, 0.0f, 1.0f, 1.0f), // blue - aiColor4D (0.3f, 1.0f, 0.3f, 1.0f), // light green - aiColor4D (0.3f, 0.3f, 1.0f, 1.0f), // light blue - aiColor4D (1.0f, 0.3f, 0.3f, 1.0f), // light red - aiColor4D (1.0f, 0.0f, 1.0f, 1.0f), // pink - aiColor4D (1.0f, 0.6f, 0.0f, 1.0f), // orange - aiColor4D (0.6f, 0.3f, 0.0f, 1.0f), // dark orange - aiColor4D (1.0f, 1.0f, 0.0f, 1.0f), // yellow - aiColor4D (0.3f, 0.3f, 0.3f, 1.0f), // dark gray - aiColor4D (0.8f, 0.8f, 0.8f, 1.0f), // light gray - aiColor4D (0.0f, 00.f, 0.0f, 1.0f), // black - aiColor4D (1.0f, 1.0f, 1.0f, 1.0f), // white - aiColor4D (0.6f, 0.0f, 1.0f, 1.0f) // violet +// color indices for DXF - 256 are supported, the table is +// taken directly from the AutoCad Index (ACI) table +// https://gohtx.com/acadcolors.php +//STH 2024-0126 +static const aiColor4D g_aclrDxfIndexColors[256] = { + aiColor4D (0.0f, 0.0f ,0.0f, 1.0f), //dxf color code 0 + aiColor4D (1.0f, 0.0f ,0.0f, 1.0f), //dxf color code 1 + aiColor4D (1.0f, 1.0f ,0.0f, 1.0f), //dxf color code 2 + aiColor4D (0.0f, 1.0f ,0.0f, 1.0f), //dxf color code 3 + aiColor4D (0.0f, 1.0f ,1.0f, 1.0f), //dxf color code 4 + aiColor4D (0.0f, 0.0f ,1.0f, 1.0f), //dxf color code 5 + aiColor4D (1.0f, 0.0f ,1.0f, 1.0f), //dxf color code 6 + aiColor4D (1.0f, 1.0f ,1.0f, 1.0f), //dxf color code 7 + aiColor4D (0.3f, 0.3f ,0.3f, 1.0f), //dxf color code 8 + aiColor4D (0.5f, 0.5f ,0.5f, 1.0f), //dxf color code 9 + aiColor4D (1.0f, 0.0f ,0.0f, 1.0f), //dxf color code 10 + aiColor4D (1.0f, 0.7f ,0.7f, 1.0f), //dxf color code 11 + aiColor4D (0.7f, 0.0f ,0.0f, 1.0f), //dxf color code 12 + aiColor4D (0.7f, 0.5f ,0.5f, 1.0f), //dxf color code 13 + aiColor4D (0.5f, 0.0f ,0.0f, 1.0f), //dxf color code 14 + aiColor4D (0.5f, 0.3f ,0.3f, 1.0f), //dxf color code 15 + aiColor4D (0.4f, 0.0f ,0.0f, 1.0f), //dxf color code 16 + aiColor4D (0.4f, 0.3f ,0.3f, 1.0f), //dxf color code 17 + aiColor4D (0.3f, 0.0f ,0.0f, 1.0f), //dxf color code 18 + aiColor4D (0.3f, 0.2f ,0.2f, 1.0f), //dxf color code 19 + aiColor4D (1.0f, 0.2f ,0.0f, 1.0f), //dxf color code 20 + aiColor4D (1.0f, 0.7f ,0.7f, 1.0f), //dxf color code 21 + aiColor4D (0.7f, 0.2f ,0.0f, 1.0f), //dxf color code 22 + aiColor4D (0.7f, 0.6f ,0.5f, 1.0f), //dxf color code 23 + aiColor4D (0.5f, 0.1f ,0.0f, 1.0f), //dxf color code 24 + aiColor4D (0.5f, 0.4f ,0.3f, 1.0f), //dxf color code 25 + aiColor4D (0.4f, 0.1f ,0.0f, 1.0f), //dxf color code 26 + aiColor4D (0.4f, 0.3f ,0.3f, 1.0f), //dxf color code 27 + aiColor4D (0.3f, 0.1f ,0.0f, 1.0f), //dxf color code 28 + aiColor4D (0.3f, 0.2f ,0.2f, 1.0f), //dxf color code 29 + aiColor4D (1.0f, 0.5f ,0.0f, 1.0f), //dxf color code 30 + aiColor4D (1.0f, 0.8f ,0.7f, 1.0f), //dxf color code 31 + aiColor4D (0.7f, 0.4f ,0.0f, 1.0f), //dxf color code 32 + aiColor4D (0.7f, 0.6f ,0.5f, 1.0f), //dxf color code 33 + aiColor4D (0.5f, 0.3f ,0.0f, 1.0f), //dxf color code 34 + aiColor4D (0.5f, 0.4f ,0.3f, 1.0f), //dxf color code 35 + aiColor4D (0.4f, 0.2f ,0.0f, 1.0f), //dxf color code 36 + aiColor4D (0.4f, 0.3f ,0.3f, 1.0f), //dxf color code 37 + aiColor4D (0.3f, 0.2f ,0.0f, 1.0f), //dxf color code 38 + aiColor4D (0.3f, 0.3f ,0.2f, 1.0f), //dxf color code 39 + aiColor4D (1.0f, 0.7f ,0.0f, 1.0f), //dxf color code 40 + aiColor4D (1.0f, 0.9f ,0.7f, 1.0f), //dxf color code 41 + aiColor4D (0.7f, 0.6f ,0.0f, 1.0f), //dxf color code 42 + aiColor4D (0.7f, 0.7f ,0.5f, 1.0f), //dxf color code 43 + aiColor4D (0.5f, 0.4f ,0.0f, 1.0f), //dxf color code 44 + aiColor4D (0.5f, 0.5f ,0.3f, 1.0f), //dxf color code 45 + aiColor4D (0.4f, 0.3f ,0.0f, 1.0f), //dxf color code 46 + aiColor4D (0.4f, 0.4f ,0.3f, 1.0f), //dxf color code 47 + aiColor4D (0.3f, 0.2f ,0.0f, 1.0f), //dxf color code 48 + aiColor4D (0.3f, 0.3f ,0.2f, 1.0f), //dxf color code 49 + aiColor4D (1.0f, 1.0f ,0.0f, 1.0f), //dxf color code 50 + aiColor4D (1.0f, 1.0f ,0.7f, 1.0f), //dxf color code 51 + aiColor4D (0.7f, 0.7f ,0.0f, 1.0f), //dxf color code 52 + aiColor4D (0.7f, 0.7f ,0.5f, 1.0f), //dxf color code 53 + aiColor4D (0.5f, 0.5f ,0.0f, 1.0f), //dxf color code 54 + aiColor4D (0.5f, 0.5f ,0.3f, 1.0f), //dxf color code 55 + aiColor4D (0.4f, 0.4f ,0.0f, 1.0f), //dxf color code 56 + aiColor4D (0.4f, 0.4f ,0.3f, 1.0f), //dxf color code 57 + aiColor4D (0.3f, 0.3f ,0.0f, 1.0f), //dxf color code 58 + aiColor4D (0.3f, 0.3f ,0.2f, 1.0f), //dxf color code 59 + aiColor4D (0.7f, 1.0f ,0.0f, 1.0f), //dxf color code 60 + aiColor4D (0.9f, 1.0f ,0.7f, 1.0f), //dxf color code 61 + aiColor4D (0.6f, 0.7f ,0.0f, 1.0f), //dxf color code 62 + aiColor4D (0.7f, 0.7f ,0.5f, 1.0f), //dxf color code 63 + aiColor4D (0.4f, 0.5f ,0.0f, 1.0f), //dxf color code 64 + aiColor4D (0.5f, 0.5f ,0.3f, 1.0f), //dxf color code 65 + aiColor4D (0.3f, 0.4f ,0.0f, 1.0f), //dxf color code 66 + aiColor4D (0.4f, 0.4f ,0.3f, 1.0f), //dxf color code 67 + aiColor4D (0.2f, 0.3f ,0.0f, 1.0f), //dxf color code 68 + aiColor4D (0.3f, 0.3f ,0.2f, 1.0f), //dxf color code 69 + aiColor4D (0.5f, 1.0f ,0.0f, 1.0f), //dxf color code 70 + aiColor4D (0.8f, 1.0f ,0.7f, 1.0f), //dxf color code 71 + aiColor4D (0.4f, 0.7f ,0.0f, 1.0f), //dxf color code 72 + aiColor4D (0.6f, 0.7f ,0.5f, 1.0f), //dxf color code 73 + aiColor4D (0.3f, 0.5f ,0.0f, 1.0f), //dxf color code 74 + aiColor4D (0.4f, 0.5f ,0.3f, 1.0f), //dxf color code 75 + aiColor4D (0.2f, 0.4f ,0.0f, 1.0f), //dxf color code 76 + aiColor4D (0.3f, 0.4f ,0.3f, 1.0f), //dxf color code 77 + aiColor4D (0.2f, 0.3f ,0.0f, 1.0f), //dxf color code 78 + aiColor4D (0.3f, 0.3f ,0.2f, 1.0f), //dxf color code 79 + aiColor4D (0.2f, 1.0f ,0.0f, 1.0f), //dxf color code 80 + aiColor4D (0.7f, 1.0f ,0.7f, 1.0f), //dxf color code 81 + aiColor4D (0.2f, 0.7f ,0.0f, 1.0f), //dxf color code 82 + aiColor4D (0.6f, 0.7f ,0.5f, 1.0f), //dxf color code 83 + aiColor4D (0.1f, 0.5f ,0.0f, 1.0f), //dxf color code 84 + aiColor4D (0.4f, 0.5f ,0.3f, 1.0f), //dxf color code 85 + aiColor4D (0.1f, 0.4f ,0.0f, 1.0f), //dxf color code 86 + aiColor4D (0.3f, 0.4f ,0.3f, 1.0f), //dxf color code 87 + aiColor4D (0.1f, 0.3f ,0.0f, 1.0f), //dxf color code 88 + aiColor4D (0.2f, 0.3f ,0.2f, 1.0f), //dxf color code 89 + aiColor4D (0.0f, 1.0f ,0.0f, 1.0f), //dxf color code 90 + aiColor4D (0.7f, 1.0f ,0.7f, 1.0f), //dxf color code 91 + aiColor4D (0.0f, 0.7f ,0.0f, 1.0f), //dxf color code 92 + aiColor4D (0.5f, 0.7f ,0.5f, 1.0f), //dxf color code 93 + aiColor4D (0.0f, 0.5f ,0.0f, 1.0f), //dxf color code 94 + aiColor4D (0.3f, 0.5f ,0.3f, 1.0f), //dxf color code 95 + aiColor4D (0.0f, 0.4f ,0.0f, 1.0f), //dxf color code 96 + aiColor4D (0.3f, 0.4f ,0.3f, 1.0f), //dxf color code 97 + aiColor4D (0.0f, 0.3f ,0.0f, 1.0f), //dxf color code 98 + aiColor4D (0.2f, 0.3f ,0.2f, 1.0f), //dxf color code 99 + aiColor4D (0.0f, 1.0f ,0.2f, 1.0f), //dxf color code 100 + aiColor4D (0.7f, 1.0f ,0.7f, 1.0f), //dxf color code 101 + aiColor4D (0.0f, 0.7f ,0.2f, 1.0f), //dxf color code 102 + aiColor4D (0.5f, 0.7f ,0.6f, 1.0f), //dxf color code 103 + aiColor4D (0.0f, 0.5f ,0.1f, 1.0f), //dxf color code 104 + aiColor4D (0.3f, 0.5f ,0.4f, 1.0f), //dxf color code 105 + aiColor4D (0.0f, 0.4f ,0.1f, 1.0f), //dxf color code 106 + aiColor4D (0.3f, 0.4f ,0.3f, 1.0f), //dxf color code 107 + aiColor4D (0.0f, 0.3f ,0.1f, 1.0f), //dxf color code 108 + aiColor4D (0.2f, 0.3f ,0.2f, 1.0f), //dxf color code 109 + aiColor4D (0.0f, 1.0f ,0.5f, 1.0f), //dxf color code 110 + aiColor4D (0.7f, 1.0f ,0.8f, 1.0f), //dxf color code 111 + aiColor4D (0.0f, 0.7f ,0.4f, 1.0f), //dxf color code 112 + aiColor4D (0.5f, 0.7f ,0.6f, 1.0f), //dxf color code 113 + aiColor4D (0.0f, 0.5f ,0.3f, 1.0f), //dxf color code 114 + aiColor4D (0.3f, 0.5f ,0.4f, 1.0f), //dxf color code 115 + aiColor4D (0.0f, 0.4f ,0.2f, 1.0f), //dxf color code 116 + aiColor4D (0.3f, 0.4f ,0.3f, 1.0f), //dxf color code 117 + aiColor4D (0.0f, 0.3f ,0.2f, 1.0f), //dxf color code 118 + aiColor4D (0.2f, 0.3f ,0.3f, 1.0f), //dxf color code 119 + aiColor4D (0.0f, 1.0f ,0.7f, 1.0f), //dxf color code 120 + aiColor4D (0.7f, 1.0f ,0.9f, 1.0f), //dxf color code 121 + aiColor4D (0.0f, 0.7f ,0.6f, 1.0f), //dxf color code 122 + aiColor4D (0.5f, 0.7f ,0.7f, 1.0f), //dxf color code 123 + aiColor4D (0.0f, 0.5f ,0.4f, 1.0f), //dxf color code 124 + aiColor4D (0.3f, 0.5f ,0.5f, 1.0f), //dxf color code 125 + aiColor4D (0.0f, 0.4f ,0.3f, 1.0f), //dxf color code 126 + aiColor4D (0.3f, 0.4f ,0.4f, 1.0f), //dxf color code 127 + aiColor4D (0.0f, 0.3f ,0.2f, 1.0f), //dxf color code 128 + aiColor4D (0.2f, 0.3f ,0.3f, 1.0f), //dxf color code 129 + aiColor4D (0.0f, 1.0f ,1.0f, 1.0f), //dxf color code 130 + aiColor4D (0.7f, 1.0f ,1.0f, 1.0f), //dxf color code 131 + aiColor4D (0.0f, 0.7f ,0.7f, 1.0f), //dxf color code 132 + aiColor4D (0.5f, 0.7f ,0.7f, 1.0f), //dxf color code 133 + aiColor4D (0.0f, 0.5f ,0.5f, 1.0f), //dxf color code 134 + aiColor4D (0.3f, 0.5f ,0.5f, 1.0f), //dxf color code 135 + aiColor4D (0.0f, 0.4f ,0.4f, 1.0f), //dxf color code 136 + aiColor4D (0.3f, 0.4f ,0.4f, 1.0f), //dxf color code 137 + aiColor4D (0.0f, 0.3f ,0.3f, 1.0f), //dxf color code 138 + aiColor4D (0.2f, 0.3f ,0.3f, 1.0f), //dxf color code 139 + aiColor4D (0.0f, 0.7f ,1.0f, 1.0f), //dxf color code 140 + aiColor4D (0.7f, 0.9f ,1.0f, 1.0f), //dxf color code 141 + aiColor4D (0.0f, 0.6f ,0.7f, 1.0f), //dxf color code 142 + aiColor4D (0.5f, 0.7f ,0.7f, 1.0f), //dxf color code 143 + aiColor4D (0.0f, 0.4f ,0.5f, 1.0f), //dxf color code 144 + aiColor4D (0.3f, 0.5f ,0.5f, 1.0f), //dxf color code 145 + aiColor4D (0.0f, 0.3f ,0.4f, 1.0f), //dxf color code 146 + aiColor4D (0.3f, 0.4f ,0.4f, 1.0f), //dxf color code 147 + aiColor4D (0.0f, 0.2f ,0.3f, 1.0f), //dxf color code 148 + aiColor4D (0.2f, 0.3f ,0.3f, 1.0f), //dxf color code 149 + aiColor4D (0.0f, 0.5f ,1.0f, 1.0f), //dxf color code 150 + aiColor4D (0.7f, 0.8f ,1.0f, 1.0f), //dxf color code 151 + aiColor4D (0.0f, 0.4f ,0.7f, 1.0f), //dxf color code 152 + aiColor4D (0.5f, 0.6f ,0.7f, 1.0f), //dxf color code 153 + aiColor4D (0.0f, 0.3f ,0.5f, 1.0f), //dxf color code 154 + aiColor4D (0.3f, 0.4f ,0.5f, 1.0f), //dxf color code 155 + aiColor4D (0.0f, 0.2f ,0.4f, 1.0f), //dxf color code 156 + aiColor4D (0.3f, 0.3f ,0.4f, 1.0f), //dxf color code 157 + aiColor4D (0.0f, 0.2f ,0.3f, 1.0f), //dxf color code 158 + aiColor4D (0.2f, 0.3f ,0.3f, 1.0f), //dxf color code 159 + aiColor4D (0.0f, 0.2f ,1.0f, 1.0f), //dxf color code 160 + aiColor4D (0.7f, 0.7f ,1.0f, 1.0f), //dxf color code 161 + aiColor4D (0.0f, 0.2f ,0.7f, 1.0f), //dxf color code 162 + aiColor4D (0.5f, 0.6f ,0.7f, 1.0f), //dxf color code 163 + aiColor4D (0.0f, 0.1f ,0.5f, 1.0f), //dxf color code 164 + aiColor4D (0.3f, 0.4f ,0.5f, 1.0f), //dxf color code 165 + aiColor4D (0.0f, 0.1f ,0.4f, 1.0f), //dxf color code 166 + aiColor4D (0.3f, 0.3f ,0.4f, 1.0f), //dxf color code 167 + aiColor4D (0.0f, 0.1f ,0.3f, 1.0f), //dxf color code 168 + aiColor4D (0.2f, 0.2f ,0.3f, 1.0f), //dxf color code 169 + aiColor4D (0.0f, 0.0f ,1.0f, 1.0f), //dxf color code 170 + aiColor4D (0.7f, 0.7f ,1.0f, 1.0f), //dxf color code 171 + aiColor4D (0.0f, 0.0f ,0.7f, 1.0f), //dxf color code 172 + aiColor4D (0.5f, 0.5f ,0.7f, 1.0f), //dxf color code 173 + aiColor4D (0.0f, 0.0f ,0.5f, 1.0f), //dxf color code 174 + aiColor4D (0.3f, 0.3f ,0.5f, 1.0f), //dxf color code 175 + aiColor4D (0.0f, 0.0f ,0.4f, 1.0f), //dxf color code 176 + aiColor4D (0.3f, 0.3f ,0.4f, 1.0f), //dxf color code 177 + aiColor4D (0.0f, 0.0f ,0.3f, 1.0f), //dxf color code 178 + aiColor4D (0.2f, 0.2f ,0.3f, 1.0f), //dxf color code 179 + aiColor4D (0.2f, 0.0f ,1.0f, 1.0f), //dxf color code 180 + aiColor4D (0.7f, 0.7f ,1.0f, 1.0f), //dxf color code 181 + aiColor4D (0.2f, 0.0f ,0.7f, 1.0f), //dxf color code 182 + aiColor4D (0.6f, 0.5f ,0.7f, 1.0f), //dxf color code 183 + aiColor4D (0.1f, 0.0f ,0.5f, 1.0f), //dxf color code 184 + aiColor4D (0.4f, 0.3f ,0.5f, 1.0f), //dxf color code 185 + aiColor4D (0.1f, 0.0f ,0.4f, 1.0f), //dxf color code 186 + aiColor4D (0.3f, 0.3f ,0.4f, 1.0f), //dxf color code 187 + aiColor4D (0.1f, 0.0f ,0.3f, 1.0f), //dxf color code 188 + aiColor4D (0.2f, 0.2f ,0.3f, 1.0f), //dxf color code 189 + aiColor4D (0.5f, 0.0f ,1.0f, 1.0f), //dxf color code 190 + aiColor4D (0.8f, 0.7f ,1.0f, 1.0f), //dxf color code 191 + aiColor4D (0.4f, 0.0f ,0.7f, 1.0f), //dxf color code 192 + aiColor4D (0.6f, 0.5f ,0.7f, 1.0f), //dxf color code 193 + aiColor4D (0.3f, 0.0f ,0.5f, 1.0f), //dxf color code 194 + aiColor4D (0.4f, 0.3f ,0.5f, 1.0f), //dxf color code 195 + aiColor4D (0.2f, 0.0f ,0.4f, 1.0f), //dxf color code 196 + aiColor4D (0.3f, 0.3f ,0.4f, 1.0f), //dxf color code 197 + aiColor4D (0.2f, 0.0f ,0.3f, 1.0f), //dxf color code 198 + aiColor4D (0.3f, 0.2f ,0.3f, 1.0f), //dxf color code 199 + aiColor4D (0.7f, 0.0f ,1.0f, 1.0f), //dxf color code 200 + aiColor4D (0.9f, 0.7f ,1.0f, 1.0f), //dxf color code 201 + aiColor4D (0.6f, 0.0f ,0.7f, 1.0f), //dxf color code 202 + aiColor4D (0.7f, 0.5f ,0.7f, 1.0f), //dxf color code 203 + aiColor4D (0.4f, 0.0f ,0.5f, 1.0f), //dxf color code 204 + aiColor4D (0.5f, 0.3f ,0.5f, 1.0f), //dxf color code 205 + aiColor4D (0.3f, 0.0f ,0.4f, 1.0f), //dxf color code 206 + aiColor4D (0.4f, 0.3f ,0.4f, 1.0f), //dxf color code 207 + aiColor4D (0.2f, 0.0f ,0.3f, 1.0f), //dxf color code 208 + aiColor4D (0.3f, 0.2f ,0.3f, 1.0f), //dxf color code 209 + aiColor4D (1.0f, 0.0f ,1.0f, 1.0f), //dxf color code 210 + aiColor4D (1.0f, 0.7f ,1.0f, 1.0f), //dxf color code 211 + aiColor4D (0.7f, 0.0f ,0.7f, 1.0f), //dxf color code 212 + aiColor4D (0.7f, 0.5f ,0.7f, 1.0f), //dxf color code 213 + aiColor4D (0.5f, 0.0f ,0.5f, 1.0f), //dxf color code 214 + aiColor4D (0.5f, 0.3f ,0.5f, 1.0f), //dxf color code 215 + aiColor4D (0.4f, 0.0f ,0.4f, 1.0f), //dxf color code 216 + aiColor4D (0.4f, 0.3f ,0.4f, 1.0f), //dxf color code 217 + aiColor4D (0.3f, 0.0f ,0.3f, 1.0f), //dxf color code 218 + aiColor4D (0.3f, 0.2f ,0.3f, 1.0f), //dxf color code 219 + aiColor4D (1.0f, 0.0f ,0.7f, 1.0f), //dxf color code 220 + aiColor4D (1.0f, 0.7f ,0.9f, 1.0f), //dxf color code 221 + aiColor4D (0.7f, 0.0f ,0.6f, 1.0f), //dxf color code 222 + aiColor4D (0.7f, 0.5f ,0.7f, 1.0f), //dxf color code 223 + aiColor4D (0.5f, 0.0f ,0.4f, 1.0f), //dxf color code 224 + aiColor4D (0.5f, 0.3f ,0.5f, 1.0f), //dxf color code 225 + aiColor4D (0.4f, 0.0f ,0.3f, 1.0f), //dxf color code 226 + aiColor4D (0.4f, 0.3f ,0.4f, 1.0f), //dxf color code 227 + aiColor4D (0.3f, 0.0f ,0.2f, 1.0f), //dxf color code 228 + aiColor4D (0.3f, 0.2f ,0.3f, 1.0f), //dxf color code 229 + aiColor4D (1.0f, 0.0f ,0.5f, 1.0f), //dxf color code 230 + aiColor4D (1.0f, 0.7f ,0.8f, 1.0f), //dxf color code 231 + aiColor4D (0.7f, 0.0f ,0.4f, 1.0f), //dxf color code 232 + aiColor4D (0.7f, 0.5f ,0.6f, 1.0f), //dxf color code 233 + aiColor4D (0.5f, 0.0f ,0.3f, 1.0f), //dxf color code 234 + aiColor4D (0.5f, 0.3f ,0.4f, 1.0f), //dxf color code 235 + aiColor4D (0.4f, 0.0f ,0.2f, 1.0f), //dxf color code 236 + aiColor4D (0.4f, 0.3f ,0.3f, 1.0f), //dxf color code 237 + aiColor4D (0.3f, 0.0f ,0.2f, 1.0f), //dxf color code 238 + aiColor4D (0.3f, 0.2f ,0.3f, 1.0f), //dxf color code 239 + aiColor4D (1.0f, 0.0f ,0.2f, 1.0f), //dxf color code 240 + aiColor4D (1.0f, 0.7f ,0.7f, 1.0f), //dxf color code 241 + aiColor4D (0.7f, 0.0f ,0.2f, 1.0f), //dxf color code 242 + aiColor4D (0.7f, 0.5f ,0.6f, 1.0f), //dxf color code 243 + aiColor4D (0.5f, 0.0f ,0.1f, 1.0f), //dxf color code 244 + aiColor4D (0.5f, 0.3f ,0.4f, 1.0f), //dxf color code 245 + aiColor4D (0.4f, 0.0f ,0.1f, 1.0f), //dxf color code 246 + aiColor4D (0.4f, 0.3f ,0.3f, 1.0f), //dxf color code 247 + aiColor4D (0.3f, 0.0f ,0.1f, 1.0f), //dxf color code 248 + aiColor4D (0.3f, 0.2f ,0.2f, 1.0f), //dxf color code 249 + aiColor4D (0.2f, 0.2f ,0.2f, 1.0f), //dxf color code 250 + aiColor4D (0.3f, 0.3f ,0.3f, 1.0f), //dxf color code 251 + aiColor4D (0.4f, 0.4f ,0.4f, 1.0f), //dxf color code 252 + aiColor4D (0.5f, 0.5f ,0.5f, 1.0f), //dxf color code 253 + aiColor4D (0.7f, 0.7f ,0.7f, 1.0f), //dxf color code 254 + aiColor4D (1.0f, 1.0f ,1.0f, 1.0f) //dxf color code 255 }; + #define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0])) #define AI_DXF_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC" @@ -95,7 +338,7 @@ static const int GroupCode_XComp = 10; static const int GroupCode_YComp = 20; static const int GroupCode_ZComp = 30; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Drawing Interchange Format (DXF) Importer", "", "", @@ -108,14 +351,6 @@ static const aiImporterDesc desc = { "dxf" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -DXFImporter::DXFImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -DXFImporter::~DXFImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool /*checkSig*/ ) const { @@ -135,7 +370,7 @@ void DXFImporter::InternReadFile( const std::string& filename, aiScene* pScene, std::shared_ptr file = std::shared_ptr( pIOHandler->Open( filename) ); // Check whether we can read the file - if( file.get() == nullptr ) { + if (file == nullptr) { throw DeadlyImportError( "Failed to open DXF file ", filename, ""); } @@ -150,7 +385,7 @@ void DXFImporter::InternReadFile( const std::string& filename, aiScene* pScene, // DXF files can grow very large, so read them via the StreamReader, // which will choose a suitable strategy. file->Seek(0,aiOrigin_SET); - StreamReaderLE stream( file ); + StreamReaderLE stream( std::move(file) ); DXF::LineReader reader (stream); DXF::FileData output; @@ -228,7 +463,7 @@ void DXFImporter::ConvertMeshes(aiScene* pScene, DXF::FileData& output) { ASSIMP_LOG_VERBOSE_DEBUG("DXF: Unexpanded polycount is ", icount, ", vertex count is ", vcount); } - if (! output.blocks.size() ) { + if (output.blocks.empty()) { throw DeadlyImportError("DXF: no data blocks loaded"); } @@ -370,7 +605,7 @@ void DXFImporter::ExpandBlockReferences(DXF::Block& bl,const DXF::BlockMap& bloc ASSIMP_LOG_ERROR("DXF: PolyLine instance is nullptr, skipping."); continue; } - + std::shared_ptr pl_out = std::shared_ptr(new DXF::PolyLine(*pl_in)); if (bl_src.base.Length() || insert.scale.x!=1.f || insert.scale.y!=1.f || insert.scale.z!=1.f || insert.angle || insert.pos.Length()) { @@ -378,8 +613,12 @@ void DXFImporter::ExpandBlockReferences(DXF::Block& bl,const DXF::BlockMap& bloc // XXX order aiMatrix4x4 trafo, tmp; aiMatrix4x4::Translation(-bl_src.base,trafo); - trafo *= aiMatrix4x4::Scaling(insert.scale,tmp); + //Need to translate position before scaling the insert + //otherwise the position ends up being the position*scaling + //STH 2024.01.17 trafo *= aiMatrix4x4::Translation(insert.pos,tmp); + trafo *= aiMatrix4x4::Scaling(insert.scale,tmp); + //trafo *= aiMatrix4x4::Translation(insert.pos,tmp); // XXX rotation currently ignored - I didn't find an appropriate sample model. if (insert.angle != 0.f) { @@ -586,10 +825,11 @@ void DXFImporter::ParseInsertion(DXF::LineReader& reader, DXF::FileData& output) } } -#define DXF_POLYLINE_FLAG_CLOSED 0x1 -#define DXF_POLYLINE_FLAG_3D_POLYLINE 0x8 -#define DXF_POLYLINE_FLAG_3D_POLYMESH 0x10 -#define DXF_POLYLINE_FLAG_POLYFACEMESH 0x40 +static constexpr unsigned int DXF_POLYLINE_FLAG_CLOSED = 0x1; +// Currently unused +//static constexpr unsigned int DXF_POLYLINE_FLAG_3D_POLYLINE = 0x8; +//static constexpr unsigned int DXF_POLYLINE_FLAG_3D_POLYMESH = 0x10; +static constexpr unsigned int DXF_POLYLINE_FLAG_POLYFACEMESH = 0x40; // ------------------------------------------------------------------------------------------------ void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) { @@ -638,12 +878,6 @@ void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) reader++; } - //if (!(line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH)) { - // DefaultLogger::get()->warn((Formatter::format("DXF: polyline not currently supported: "),line.flags)); - // output.blocks.back().lines.pop_back(); - // return; - //} - if (vguess && line.positions.size() != vguess) { ASSIMP_LOG_WARN("DXF: unexpected vertex count in polymesh: ", line.positions.size(),", expected ", vguess ); @@ -733,12 +967,18 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li case 71: case 72: case 73: - case 74: - if (cnti == 4) { - ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring"); - break; + case 74: { + if (cnti == 4) { + ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring"); + break; + } + const int index = reader.ValueAsSignedInt(); + if (index >= 0) { + indices[cnti++] = static_cast(index); + } else { + indices[cnti++] = static_cast(-index); + } } - indices[cnti++] = reader.ValueAsUnsignedInt(); break; // color @@ -776,8 +1016,7 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li } // ------------------------------------------------------------------------------------------------ -void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output) -{ +void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output) { // (note) this is also used for for parsing line entities, so we // must handle the vertex_count == 2 case as well. @@ -794,8 +1033,7 @@ void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output) if (reader.GroupCode() == 0) { break; } - switch (reader.GroupCode()) - { + switch (reader.GroupCode()) { // 8 specifies the layer case 8: diff --git a/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.h b/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.h index b32ae106f..9230bccbd 100644 --- a/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.h +++ b/Engine/lib/assimp/code/AssetLib/DXF/DXFLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,8 +68,8 @@ namespace DXF { */ class DXFImporter : public BaseImporter { public: - DXFImporter(); - ~DXFImporter() override; + DXFImporter() = default; + ~DXFImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXAnimation.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXAnimation.cpp index af92ebe51..fdde37f24 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXAnimation.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXAnimation.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp index 8fb700179..b828090e5 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXBinaryTokenizer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXUtil.h" #include #include +#include #include #include #include @@ -139,6 +140,7 @@ size_t Offset(const char* begin, const char* cursor) { } // ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void TokenizeError(const std::string& message, const char* begin, const char* cursor) AI_WONT_RETURN_SUFFIX; void TokenizeError(const std::string& message, const char* begin, const char* cursor) { TokenizeError(message, Offset(begin, cursor)); } @@ -341,8 +343,7 @@ void ReadData(const char*& sbegin_out, const char*& send_out, const char* input, // ------------------------------------------------------------------------------------------------ -bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, const char* end, bool const is64bits) -{ +bool ReadScope(TokenList &output_tokens, StackAllocator &token_allocator, const char *input, const char *&cursor, const char *end, bool const is64bits) { // the first word contains the offset at which this block ends const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); @@ -408,7 +409,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, // XXX this is vulnerable to stack overflowing .. while(Offset(input, cursor) < end_offset - sentinel_block_length) { - ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits); + ReadScope(output_tokens, token_allocator, input, cursor, input + end_offset - sentinel_block_length, is64bits); } output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor) )); @@ -431,8 +432,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, // ------------------------------------------------------------------------------------------------ // TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent -void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length) -{ +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, StackAllocator &token_allocator) { ai_assert(input); ASSIMP_LOG_DEBUG("Tokenizing binary FBX file"); @@ -465,7 +465,7 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length) try { while (cursor < end ) { - if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) { + if (!ReadScope(output_tokens, token_allocator, input, cursor, input + length, is64bits)) { break; } } diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXCommon.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXCommon.h index c592c5649..7e0fb2553 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXCommon.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXCommon.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,6 +55,7 @@ const char NULL_RECORD[NumNullRecords] = { // 25 null bytes in 64-bit and 13 nul '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?) +static std::string NULL_RECORD_STRING(NumNullRecords, '\0'); const std::string SEPARATOR = { '\x00', '\x01' }; // for use inside strings const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import const int64_t SECOND = 46186158000; // FBX's kTime unit diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXCompileConfig.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXCompileConfig.h index 927a3c5f6..9885ca346 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXCompileConfig.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXCompileConfig.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.cpp index ffe961a4e..ca9e3adbd 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,9 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include - #include - #include #include #include @@ -78,6 +76,53 @@ using namespace Util; #define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000LL +static void correctRootTransform(const aiScene *scene) { + if (scene == nullptr) { + return; + } + + if (scene->mMetaData == nullptr) { + return; + } + + int32_t UpAxis = 1, UpAxisSign = 1, FrontAxis = 2, FrontAxisSign = 1, CoordAxis = 0, CoordAxisSign = 1; + double UnitScaleFactor = 1.0; + for (unsigned MetadataIndex = 0; MetadataIndex < scene->mMetaData->mNumProperties; ++MetadataIndex) { + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "UpAxis") == 0) { + scene->mMetaData->Get(MetadataIndex, UpAxis); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "UpAxisSign") == 0) { + scene->mMetaData->Get(MetadataIndex, UpAxisSign); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "FrontAxis") == 0) { + scene->mMetaData->Get(MetadataIndex, FrontAxis); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "FrontAxisSign") == 0) { + scene->mMetaData->Get(MetadataIndex, FrontAxisSign); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "CoordAxis") == 0) { + scene->mMetaData->Get(MetadataIndex, CoordAxis); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "CoordAxisSign") == 0) { + scene->mMetaData->Get(MetadataIndex, CoordAxisSign); + } + if (strcmp(scene->mMetaData->mKeys[MetadataIndex].C_Str(), "UnitScaleFactor") == 0) { + scene->mMetaData->Get(MetadataIndex, UnitScaleFactor); + } + } + + aiVector3D upVec, forwardVec, rightVec; + upVec[UpAxis] = UpAxisSign * static_cast(UnitScaleFactor); + forwardVec[FrontAxis] = FrontAxisSign * static_cast(UnitScaleFactor); + rightVec[CoordAxis] = CoordAxisSign * (float)UnitScaleFactor; + + aiMatrix4x4 mat(rightVec.x, rightVec.y, rightVec.z, 0.0f, + upVec.x, upVec.y, upVec.z, 0.0f, + forwardVec.x, forwardVec.y, forwardVec.z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + scene->mRootNode->mTransformation *= mat; +} + FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBones) : defaultMaterialIndex(), mMeshes(), @@ -93,6 +138,8 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo mSceneOut(out), doc(doc), mRemoveEmptyBones(removeEmptyBones) { + + // animations need to be converted first since this will // populate the node_anim_chain_bits map, which is needed // to determine which nodes need to be generated. @@ -119,7 +166,7 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo if (mat) { if (materials_converted.find(mat) == materials_converted.end()) { - ConvertMaterial(*mat, 0); + ConvertMaterial(*mat, nullptr); } } } @@ -133,6 +180,10 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo // need not contain geometry (i.e. camera animations, raw armatures). if (out->mNumMeshes == 0) { out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } else { + // Apply the FBX axis metadata unless requested not to + if (!doc.Settings().ignoreUpDirection) + correctRootTransform(mSceneOut); } } @@ -196,7 +247,7 @@ struct FBXConverter::PotentialNode { /// todo: get bone from stack /// todo: make map of aiBone* to aiNode* /// then update convert clusters to the new format -void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) { +void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node, const aiMatrix4x4& parent_transform) { const std::vector &conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); std::vector nodes; @@ -227,7 +278,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) if (nullptr != model) { nodes_chain.clear(); post_nodes_chain.clear(); - aiMatrix4x4 new_abs_transform = parent->mTransformation; + aiMatrix4x4 new_abs_transform = parent_transform; std::string node_name = FixNodeName(model->Name()); // even though there is only a single input node, the design of // assimp (or rather: the complicated transformation chain that @@ -261,6 +312,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) child->mParent = last_parent; last_parent = child.mNode; + + new_abs_transform *= child->mTransformation; } // attach geometry @@ -283,6 +336,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) postnode->mParent = last_parent; last_parent = postnode.mNode; + + new_abs_transform *= postnode->mTransformation; } } else { // free the nodes we allocated as we don't need them @@ -290,7 +345,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) } // recursion call - child nodes - ConvertNodes(model->ID(), last_parent, root_node); + ConvertNodes(model->ID(), last_parent, root_node, new_abs_transform); if (doc.Settings().readLights) { ConvertLights(*model, node_name); @@ -305,18 +360,15 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) } } - if (nodes.size()) { - parent->mChildren = new aiNode *[nodes.size()](); - parent->mNumChildren = static_cast(nodes.size()); - - for (unsigned int i = 0; i < nodes.size(); ++i) - { - parent->mChildren[i] = nodes[i].mOwnership.release(); - } - nodes.clear(); - } else { + if (nodes.empty()) { parent->mNumChildren = 0; parent->mChildren = nullptr; + } else { + parent->mChildren = new aiNode *[nodes.size()](); + parent->mNumChildren = static_cast(nodes.size()); + for (unsigned int i = 0; i < nodes.size(); ++i) { + parent->mChildren[i] = nodes[i].mOwnership.release(); + } } } @@ -424,16 +476,32 @@ void FBXConverter::ConvertCamera(const Camera &cam, const std::string &orig_name out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight(); + // NOTE: Camera mPosition, mLookAt and mUp must be set to default here. + // All transformations to the camera will be handled by its node in the scenegraph. out_camera->mPosition = aiVector3D(0.0f); out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f); out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f); - out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); + // NOTE: Some software (maya) does not put FieldOfView in FBX, so we compute + // mHorizontalFOV from FocalLength and FilmWidth with unit conversion. - out_camera->mClipPlaneNear = cam.NearPlane(); - out_camera->mClipPlaneFar = cam.FarPlane(); + // TODO: This is not a complete solution for how FBX cameras can be stored. + // TODO: Incorporate non-square pixel aspect ratio. + // TODO: FBX aperture mode might be storing vertical FOV in need of conversion with aspect ratio. + + float fov_deg = cam.FieldOfView(); + // If FOV not specified in file, compute using FilmWidth and FocalLength. + if (fov_deg == kFovUnknown) { + float film_width_inches = cam.FilmWidth(); + float focal_length_mm = cam.FocalLength(); + ASSIMP_LOG_VERBOSE_DEBUG("FBX FOV unspecified. Computing from FilmWidth (", film_width_inches, "inches) and FocalLength (", focal_length_mm, "mm)."); + double half_fov_rad = std::atan2(film_width_inches * 25.4 * 0.5, focal_length_mm); + out_camera->mHorizontalFOV = static_cast(half_fov_rad); + } else { + // FBX fov is full-view degrees. We want half-view radians. + out_camera->mHorizontalFOV = AI_DEG_TO_RAD(fov_deg) * 0.5f; + } - out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); out_camera->mClipPlaneNear = cam.NearPlane(); out_camera->mClipPlaneFar = cam.FarPlane(); } @@ -562,16 +630,17 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot bool is_id[3] = { true, true, true }; aiMatrix4x4 temp[3]; - if (std::fabs(rotation.z) > angle_epsilon) { - aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z), temp[2]); + const auto rot = AI_DEG_TO_RAD(rotation); + if (std::fabs(rot.z) > angle_epsilon) { + aiMatrix4x4::RotationZ(rot.z, temp[2]); is_id[2] = false; } - if (std::fabs(rotation.y) > angle_epsilon) { - aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y), temp[1]); + if (std::fabs(rot.y) > angle_epsilon) { + aiMatrix4x4::RotationY(rot.y, temp[1]); is_id[1] = false; } - if (std::fabs(rotation.x) > angle_epsilon) { - aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x), temp[0]); + if (std::fabs(rot.x) > angle_epsilon) { + aiMatrix4x4::RotationX(rot.x, temp[0]); is_id[0] = false; } @@ -643,13 +712,12 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot bool FBXConverter::NeedsComplexTransformationChain(const Model &model) { const PropertyTable &props = model.Props(); - const auto zero_epsilon = ai_epsilon; + const auto zero_epsilon = Math::getEpsilon(); const aiVector3D all_ones(1.0f, 1.0f, 1.0f); for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { const TransformationComp comp = static_cast(i); - if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation || - comp == TransformationComp_PreRotation || comp == TransformationComp_PostRotation) { + if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation) { continue; } @@ -876,8 +944,12 @@ void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) { data->Set(index++, prop.first, interpretedBool->Value()); } else if (const TypedProperty *interpretedInt = prop.second->As>()) { data->Set(index++, prop.first, interpretedInt->Value()); + } else if (const TypedProperty *interpretedUInt = prop.second->As>()) { + data->Set(index++, prop.first, interpretedUInt->Value()); } else if (const TypedProperty *interpretedUint64 = prop.second->As>()) { data->Set(index++, prop.first, interpretedUint64->Value()); + } else if (const TypedProperty *interpretedint64 = prop.second->As>()) { + data->Set(index++, prop.first, interpretedint64->Value()); } else if (const TypedProperty *interpretedFloat = prop.second->As>()) { data->Set(index++, prop.first, interpretedFloat->Value()); } else if (const TypedProperty *interpretedString = prop.second->As>()) { @@ -1179,19 +1251,27 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c std::vector animMeshes; for (const BlendShape *blendShape : mesh.GetBlendShapes()) { for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { - const std::vector &shapeGeometries = blendShapeChannel->GetShapeGeometries(); - for (size_t i = 0; i < shapeGeometries.size(); i++) { - aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); - const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); - const std::vector &curVertices = shapeGeometry->GetVertices(); - const std::vector &curNormals = shapeGeometry->GetNormals(); - const std::vector &curIndices = shapeGeometry->GetIndices(); + const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (const ShapeGeometry *shapeGeometry : shapeGeometries) { + const auto &curNormals = shapeGeometry->GetNormals(); + aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty()); + const auto &curVertices = shapeGeometry->GetVertices(); + const auto &curIndices = shapeGeometry->GetIndices(); //losing channel name if using shapeGeometry->Name() - animMesh->mName.Set(FixAnimMeshName(blendShapeChannel->Name())); + // if blendShapeChannel Name is empty or doesn't have a ".", add geoMetryName; + auto aniName = FixAnimMeshName(blendShapeChannel->Name()); + auto geoMetryName = FixAnimMeshName(shapeGeometry->Name()); + if (aniName.empty()) { + aniName = geoMetryName; + } + else if (aniName.find('.') == aniName.npos) { + aniName += "." + geoMetryName; + } + animMesh->mName.Set(aniName); for (size_t j = 0; j < curIndices.size(); j++) { const unsigned int curIndex = curIndices.at(j); aiVector3D vertex = curVertices.at(j); - aiVector3D normal = curNormals.at(j); + aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j); unsigned int count = 0; const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count); for (unsigned int k = 0; k < count; k++) { @@ -1409,18 +1489,17 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co std::vector animMeshes; for (const BlendShape *blendShape : mesh.GetBlendShapes()) { for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { - const std::vector &shapeGeometries = blendShapeChannel->GetShapeGeometries(); - for (size_t i = 0; i < shapeGeometries.size(); i++) { - aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); - const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); - const std::vector &curVertices = shapeGeometry->GetVertices(); - const std::vector &curNormals = shapeGeometry->GetNormals(); - const std::vector &curIndices = shapeGeometry->GetIndices(); + const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (const ShapeGeometry *shapeGeometry : shapeGeometries) { + const auto& curNormals = shapeGeometry->GetNormals(); + aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty()); + const auto& curVertices = shapeGeometry->GetVertices(); + const auto& curIndices = shapeGeometry->GetIndices(); animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name())); for (size_t j = 0; j < curIndices.size(); j++) { unsigned int curIndex = curIndices.at(j); aiVector3D vertex = curVertices.at(j); - aiVector3D normal = curNormals.at(j); + aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j); unsigned int count = 0; const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count); for (unsigned int k = 0; k < count; k++) { @@ -1458,7 +1537,9 @@ static void copyBoneToSkeletonBone(aiMesh *mesh, aiBone *bone, aiSkeletonBone *s skeletonBone->mWeights = bone->mWeights; skeletonBone->mOffsetMatrix = bone->mOffsetMatrix; skeletonBone->mMeshId = mesh; +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS skeletonBone->mNode = bone->mNode; +#endif skeletonBone->mParent = -1; } @@ -1566,7 +1647,7 @@ void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo, const ai out->mBones = nullptr; out->mNumBones = 0; return; - } + } out->mBones = new aiBone *[bones.size()](); out->mNumBones = static_cast(bones.size()); @@ -1575,7 +1656,7 @@ void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo, const ai void FBXConverter::ConvertCluster(std::vector &local_mesh_bones, const Cluster *cluster, std::vector &out_indices, std::vector &index_out_indices, - std::vector &count_out_indices, const aiMatrix4x4 & /* absolute_transform*/, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, aiNode *) { ai_assert(cluster != nullptr); // make sure cluster valid @@ -1592,16 +1673,16 @@ void FBXConverter::ConvertCluster(std::vector &local_mesh_bones, const bone = new aiBone(); bone->mName = bone_name; - bone->mOffsetMatrix = cluster->Transform(); + //bone->mOffsetMatrix = cluster->Transform(); // store local transform link for post processing - /* + bone->mOffsetMatrix = cluster->TransformLink(); bone->mOffsetMatrix.Inverse(); - aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; + const aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset - */ + // // Now calculate the aiVertexWeights // @@ -1784,7 +1865,7 @@ aiString FBXConverter::GetTexturePath(const Texture *tex) { // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it // This may occur on this case too, it has to be studied path.data[0] = '*'; - path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + path.length = 1 + ASSIMP_itoa10(path.data + 1, AI_MAXLEN - 1, index); } } } @@ -2052,6 +2133,10 @@ void FBXConverter::SetTextureProperties(aiMaterial *out_mat, const TextureMap &_ TrySetTextureProperties(out_mat, _textures, "Maya|emissionColor", aiTextureType_EMISSION_COLOR, mesh); TrySetTextureProperties(out_mat, _textures, "Maya|metalness", aiTextureType_METALNESS, mesh); TrySetTextureProperties(out_mat, _textures, "Maya|diffuseRoughness", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|base", aiTextureType_MAYA_BASE, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|specular", aiTextureType_MAYA_SPECULAR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|specularColor", aiTextureType_MAYA_SPECULAR_COLOR, mesh); + TrySetTextureProperties(out_mat, _textures, "Maya|specularRoughness", aiTextureType_MAYA_SPECULAR_ROUGHNESS, mesh); // Maya stingray TrySetTextureProperties(out_mat, _textures, "Maya|TEX_color_map", aiTextureType_BASE_COLOR, mesh); @@ -2364,7 +2449,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial *out_mat, const PropertyTa // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) path.data[0] = '*'; - path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + path.length = 1 + ASSIMP_itoa10(path.data + 1, AI_MAXLEN - 1, index); } out_mat->AddProperty(&path, (name + "|file").c_str(), aiTextureType_UNKNOWN, 0); @@ -2730,7 +2815,7 @@ void FBXConverter::ProcessMorphAnimDatas(std::map auto geoIt = std::find(model->GetGeometry().begin(), model->GetGeometry().end(), geo); auto geoIndex = static_cast(std::distance(model->GetGeometry().begin(), geoIt)); auto name = aiString(FixNodeName(model->Name() + "*")); - name.length = 1 + ASSIMP_itoa10(name.data + name.length, MAXLEN - 1, geoIndex); + name.length = 1 + ASSIMP_itoa10(name.data + name.length, AI_MAXLEN - 1, geoIndex); morphAnimData *animData; auto animIt = morphAnimDatas->find(name.C_Str()); if (animIt == morphAnimDatas->end()) { @@ -3197,7 +3282,6 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name, aiVector3D defTranslate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f)); aiVector3D defRotation = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f)); aiVector3D defScale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f)); - aiQuaternion defQuat = EulerToQuaternion(defRotation, rotOrder); aiVectorKey* outTranslations = new aiVectorKey[keyCount]; aiQuatKey* outRotations = new aiQuatKey[keyCount]; @@ -3215,6 +3299,7 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name, if (keyframeLists[TransformationComp_Rotation].size() > 0) { InterpolateKeys(outRotations, keytimes, keyframeLists[TransformationComp_Rotation], defRotation, maxTime, minTime, rotOrder); } else { + aiQuaternion defQuat = EulerToQuaternion(defRotation, rotOrder); for (size_t i = 0; i < keyCount; ++i) { outRotations[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps; outRotations[i].mValue = defQuat; @@ -3231,7 +3316,7 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name, } bool ok = false; - + const auto zero_epsilon = ai_epsilon; const aiVector3D& preRotation = PropertyGet(props, "PreRotation", ok); diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.h index 41acb6ffe..096c2619f 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXConverter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -134,7 +134,7 @@ private: // ------------------------------------------------------------------------------------------------ // collect and assign child nodes - void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node); + void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node, const aiMatrix4x4& parent_transform = aiMatrix4x4()); // ------------------------------------------------------------------------------------------------ void ConvertLights(const Model& model, const std::string &orig_name ); diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXDeformer.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXDeformer.cpp index df134a401..582042360 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXDeformer.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXDeformer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -84,7 +84,7 @@ Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const transform = ReadMatrix(Transform); transformLink = ReadMatrix(TransformLink); - // it is actually possible that there be Deformer's with no weights + // it is actually possible that there are Deformer's with no weights if (!!Indexes != !!Weights) { DOMError("either Indexes or Weights are missing from Cluster",&element); } @@ -154,8 +154,10 @@ BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, for (const Connection* con : conns) { const BlendShapeChannel* const bspc = ProcessSimpleConnection(*con, false, "BlendShapeChannel -> BlendShape", element); if (bspc) { - blendShapeChannels.push_back(bspc); - continue; + auto pr = blendShapeChannels.insert(bspc); + if (!pr.second) { + FBXImporter::LogWarn("there is the same blendShapeChannel id ", bspc->ID()); + } } } } @@ -179,8 +181,10 @@ BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const for (const Connection* con : conns) { const ShapeGeometry* const sg = ProcessSimpleConnection(*con, false, "Shape -> BlendShapeChannel", element); if (sg) { - shapeGeometries.push_back(sg); - continue; + auto pr = shapeGeometries.insert(sg); + if (!pr.second) { + FBXImporter::LogWarn("there is the same shapeGeometrie id ", sg->ID()); + } } } } diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.cpp index 79a7509f4..3fb0964c4 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -78,7 +78,7 @@ const Object* LazyObject::Get(bool dieOnError) { return nullptr; } - if (object.get()) { + if (object) { return object.get(); } @@ -199,6 +199,14 @@ const Object* LazyObject::Get(bool dieOnError) { object.reset(new AnimationCurveNode(id,element,name,doc)); } } + catch (std::bad_alloc&) { + // out-of-memory is unrecoverable and should always lead to a failure + + flags &= ~BEING_CONSTRUCTED; + flags |= FAILED_TO_CONSTRUCT; + + throw; + } catch(std::exception& ex) { flags &= ~BEING_CONSTRUCTED; flags |= FAILED_TO_CONSTRUCT; @@ -214,7 +222,7 @@ const Object* LazyObject::Get(bool dieOnError) { return nullptr; } - if (!object.get()) { + if (!object) { //DOMError("failed to convert element to DOM object, class: " + classtag + ", name: " + name,&element); } @@ -235,7 +243,7 @@ FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptrCompound(); for(const ElementMap::value_type& el : sobjects.Elements()) { @@ -373,11 +387,13 @@ void Document::ReadObjects() { DOMError("encountered object with implicitly defined id 0",el.second); } - if(objects.find(id) != objects.end()) { + const auto foundObject = objects.find(id); + if(foundObject != objects.end()) { DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second); + delete_LazyObject(foundObject->second); } - objects[id] = new LazyObject(id, *el.second, *this); + objects[id] = new_LazyObject(id, *el.second, *this); // grab all animation stacks upfront since there is no listing of them if(!strcmp(el.first.c_str(),"AnimationStack")) { @@ -444,8 +460,10 @@ void Document::ReadPropertyTemplates() { } // ------------------------------------------------------------------------------------------------ -void Document::ReadConnections() { - const Scope& sc = parser.GetRootScope(); +void Document::ReadConnections() +{ + StackAllocator &allocator = parser.GetAllocator(); + const Scope &sc = parser.GetRootScope(); // read property templates from "Definitions" section const Element* const econns = sc["Connections"]; if(!econns || !econns->Compound()) { @@ -484,7 +502,7 @@ void Document::ReadConnections() { } // add new connection - const Connection* const c = new Connection(insertionOrder++,src,dest,prop,*this); + const Connection* const c = new_Connection(insertionOrder++,src,dest,prop,*this); src_connections.insert(ConnectionMap::value_type(src,c)); dest_connections.insert(ConnectionMap::value_type(dest,c)); } diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.h index c362e6475..a103321c0 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocument.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_DOCUMENT_H #include +#include #include #include #include "FBXProperties.h" @@ -54,9 +55,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define _AI_CONCAT(a,b) a ## b #define AI_CONCAT(a,b) _AI_CONCAT(a,b) + namespace Assimp { namespace FBX { +// Use an 'illegal' default FOV value to detect if the FBX camera has set the FOV. +static const float kFovUnknown = -1.0f; + + class Parser; class Object; struct ImportSettings; @@ -80,6 +86,10 @@ class BlendShape; class Skin; class Cluster; +#define new_LazyObject new (allocator.Allocate(sizeof(LazyObject))) LazyObject +#define new_Connection new (allocator.Allocate(sizeof(Connection))) Connection +#define delete_LazyObject(_p) (_p)->~LazyObject() +#define delete_Connection(_p) (_p)->~Connection() /** Represents a delay-parsed FBX objects. Many objects in the scene * are not needed by assimp, so it makes no sense to parse them @@ -168,7 +178,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } private: @@ -242,7 +252,7 @@ public: fbx_simple_property(FilmAspectRatio, float, 1.0f) fbx_simple_property(ApertureMode, int, 0) - fbx_simple_property(FieldOfView, float, 1.0f) + fbx_simple_property(FieldOfView, float, kFovUnknown) fbx_simple_property(FocalLength, float, 1.0f) }; @@ -432,7 +442,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } /** Get material links */ @@ -503,7 +513,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } // return a 4-tuple @@ -618,7 +628,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } const uint8_t* Content() const { @@ -632,7 +642,7 @@ public: uint8_t* RelinquishContent() { uint8_t* ptr = content; - content = 0; + content = nullptr; return ptr; } @@ -663,7 +673,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } const TextureMap& Textures() const { @@ -735,7 +745,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } @@ -780,7 +790,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } /* the optional white list specifies a list of property names for which the caller @@ -808,7 +818,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } const AnimationLayerList& Layers() const { @@ -829,7 +839,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } private: @@ -855,14 +865,14 @@ public: return fullWeights; } - const std::vector& GetShapeGeometries() const { + const std::unordered_set& GetShapeGeometries() const { return shapeGeometries; } private: float percent; WeightArray fullWeights; - std::vector shapeGeometries; + std::unordered_set shapeGeometries; }; /** DOM class for BlendShape deformers */ @@ -872,12 +882,12 @@ public: virtual ~BlendShape(); - const std::vector& BlendShapeChannels() const { + const std::unordered_set& BlendShapeChannels() const { return blendShapeChannels; } private: - std::vector blendShapeChannels; + std::unordered_set blendShapeChannels; }; /** DOM class for skin deformer clusters (aka sub-deformers) */ @@ -1018,7 +1028,7 @@ public: const PropertyTable& Props() const { ai_assert(props.get()); - return *props.get(); + return *props; } const Document& GetDocument() const { @@ -1072,7 +1082,7 @@ private: /** DOM root for a FBX file */ class Document { public: - Document(const Parser& parser, const ImportSettings& settings); + Document(Parser& parser, const ImportSettings& settings); ~Document(); @@ -1097,7 +1107,7 @@ public: const FileGlobalSettings& GlobalSettings() const { ai_assert(globals.get()); - return *globals.get(); + return *globals; } const PropertyTemplateMap& Templates() const { @@ -1156,7 +1166,7 @@ private: const ImportSettings& settings; ObjectMap objects; - const Parser& parser; + Parser& parser; PropertyTemplateMap templates; ConnectionMap src_connections; diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp index c41eb2747..64105f351 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.h index d32c12e1a..1e4653201 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXDocumentUtil.h @@ -58,12 +58,11 @@ namespace Util { /* DOM/Parse error reporting - does not return */ AI_WONT_RETURN void DOMError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX; -AI_WONT_RETURN void DOMError(const std::string& message, const Element* element = NULL) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN void DOMError(const std::string &message, const Element *element = nullptr) AI_WONT_RETURN_SUFFIX; // does return void DOMWarning(const std::string& message, const Token& token); -void DOMWarning(const std::string& message, const Element* element = NULL); - +void DOMWarning(const std::string &message, const Element *element = nullptr); // fetch a property table and the corresponding property template std::shared_ptr GetPropertyTable(const Document& doc, diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.cpp index 21c61b257..ae9586968 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -360,7 +360,7 @@ void FBX::Node::EndBinary( bool has_children ) { // if there were children, add a null record - if (has_children) { s.PutString(Assimp::FBX::NULL_RECORD); } + if (has_children) { s.PutString(Assimp::FBX::NULL_RECORD_STRING); } // now go back and write initial pos this->end_pos = s.Tell(); diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.h index c6c4549d5..7661ab1be 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportNode.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // StreamWriterLE #include +#include #include namespace Assimp { @@ -76,34 +77,30 @@ public: // constructors /// The class constructor with the name. Node(const std::string& n) : name(n) - , properties() - , children() , force_has_children( false ) { // empty } // convenience template to construct with properties directly template - Node(const std::string& n, const More... more) + Node(const std::string& n, More&&... more) : name(n) - , properties() - , children() , force_has_children(false) { - AddProperties(more...); + AddProperties(std::forward(more)...); } public: // functions to add properties or children // add a single property to the node template - void AddProperty(T value) { - properties.emplace_back(value); + void AddProperty(T&& value) { + properties.emplace_back(std::forward(value)); } // convenience function to add multiple properties at once template - void AddProperties(T value, More... more) { - properties.emplace_back(value); - AddProperties(more...); + void AddProperties(T&& value, More&&... more) { + properties.emplace_back(std::forward(value)); + AddProperties(std::forward(more)...); } void AddProperties() {} @@ -114,11 +111,11 @@ public: // functions to add properties or children template void AddChild( const std::string& name, - More... more + More&&... more ) { FBX::Node c(name); - c.AddProperties(more...); - children.push_back(c); + c.AddProperties(std::forward(more)...); + children.push_back(std::move(c)); } public: // support specifically for dealing with Properties70 nodes @@ -146,10 +143,10 @@ public: // support specifically for dealing with Properties70 nodes const std::string& type, const std::string& type2, const std::string& flags, - More... more + More&&... more ) { Node n("P"); - n.AddProperties(name, type, type2, flags, more...); + n.AddProperties(name, type, type2, flags, std::forward(more)...); AddChild(n); } @@ -214,7 +211,7 @@ public: // static member functions bool binary, int indent ) { FBX::FBXExportProperty p(value); - FBX::Node node(name, p); + FBX::Node node(name, std::move(p)); node.Dump(s, binary, indent); } diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.cpp index 3216d7d85..5fbe84fa7 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.h index 26d0cf223..93f8cfbe0 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExportProperty.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.cpp index 563ac68f0..80339af22 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -58,23 +58,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // Header files, standard library. -#include // shared_ptr -#include -#include // stringstream +#include #include // localtime, tm_* #include -#include -#include -#include -#include +#include // shared_ptr #include +#include +#include // stringstream +#include +#include +#include +#include +#include // RESOURCES: // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ // https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure -const ai_real DEG = ai_real( 57.29577951308232087679815481 ); // degrees per radian - using namespace Assimp; using namespace Assimp::FBX; @@ -390,7 +390,7 @@ void FBXExporter::WriteHeaderExtension () raw[i] = uint8_t(GENERIC_FILEID[i]); } FBX::Node::WritePropertyNode( - "FileId", raw, outstream, binary, indent + "FileId", std::move(raw), outstream, binary, indent ); FBX::Node::WritePropertyNode( "CreationTime", GENERIC_CTIME, outstream, binary, indent @@ -680,9 +680,9 @@ void FBXExporter::WriteDefinitions () pt = FBX::Node("PropertyTemplate", "FBXAnimLayer"); p = FBX::Node("Properties70"); p.AddP70("Weight", "Number", "", "A", double(100)); - p.AddP70bool("Mute", 0); - p.AddP70bool("Solo", 0); - p.AddP70bool("Lock", 0); + p.AddP70bool("Mute", false); + p.AddP70bool("Solo", false); + p.AddP70bool("Lock", false); p.AddP70color("Color", 0.8, 0.8, 0.8); p.AddP70("BlendMode", "enum", "", "", int32_t(0)); p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0)); @@ -732,42 +732,42 @@ void FBXExporter::WriteDefinitions () p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0); p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0); p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0); - p.AddP70bool("TranslationActive", 0); + p.AddP70bool("TranslationActive", false); p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0); p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0); - p.AddP70bool("TranslationMinX", 0); - p.AddP70bool("TranslationMinY", 0); - p.AddP70bool("TranslationMinZ", 0); - p.AddP70bool("TranslationMaxX", 0); - p.AddP70bool("TranslationMaxY", 0); - p.AddP70bool("TranslationMaxZ", 0); + p.AddP70bool("TranslationMinX", false); + p.AddP70bool("TranslationMinY", false); + p.AddP70bool("TranslationMinZ", false); + p.AddP70bool("TranslationMaxX", false); + p.AddP70bool("TranslationMaxY", false); + p.AddP70bool("TranslationMaxZ", false); p.AddP70enum("RotationOrder", 0); - p.AddP70bool("RotationSpaceForLimitOnly", 0); + p.AddP70bool("RotationSpaceForLimitOnly", false); p.AddP70double("RotationStiffnessX", 0.0); p.AddP70double("RotationStiffnessY", 0.0); p.AddP70double("RotationStiffnessZ", 0.0); p.AddP70double("AxisLen", 10.0); p.AddP70vector("PreRotation", 0.0, 0.0, 0.0); p.AddP70vector("PostRotation", 0.0, 0.0, 0.0); - p.AddP70bool("RotationActive", 0); + p.AddP70bool("RotationActive", false); p.AddP70vector("RotationMin", 0.0, 0.0, 0.0); p.AddP70vector("RotationMax", 0.0, 0.0, 0.0); - p.AddP70bool("RotationMinX", 0); - p.AddP70bool("RotationMinY", 0); - p.AddP70bool("RotationMinZ", 0); - p.AddP70bool("RotationMaxX", 0); - p.AddP70bool("RotationMaxY", 0); - p.AddP70bool("RotationMaxZ", 0); + p.AddP70bool("RotationMinX", false); + p.AddP70bool("RotationMinY", false); + p.AddP70bool("RotationMinZ", false); + p.AddP70bool("RotationMaxX", false); + p.AddP70bool("RotationMaxY", false); + p.AddP70bool("RotationMaxZ", false); p.AddP70enum("InheritType", 0); - p.AddP70bool("ScalingActive", 0); + p.AddP70bool("ScalingActive", false); p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0); p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0); - p.AddP70bool("ScalingMinX", 0); - p.AddP70bool("ScalingMinY", 0); - p.AddP70bool("ScalingMinZ", 0); - p.AddP70bool("ScalingMaxX", 0); - p.AddP70bool("ScalingMaxY", 0); - p.AddP70bool("ScalingMaxZ", 0); + p.AddP70bool("ScalingMinX", false); + p.AddP70bool("ScalingMinY", false); + p.AddP70bool("ScalingMinZ", false); + p.AddP70bool("ScalingMaxX", false); + p.AddP70bool("ScalingMaxY", false); + p.AddP70bool("ScalingMaxZ", false); p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0); p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0); p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0); @@ -788,11 +788,11 @@ void FBXExporter::WriteDefinitions () p.AddP70double("PreferedAngleZ", 0.0); p.AddP70("LookAtProperty", "object", "", ""); p.AddP70("UpVectorProperty", "object", "", ""); - p.AddP70bool("Show", 1); - p.AddP70bool("NegativePercentShapeSupport", 1); + p.AddP70bool("Show", true); + p.AddP70bool("NegativePercentShapeSupport", true); p.AddP70int("DefaultAttributeIndex", -1); - p.AddP70bool("Freeze", 0); - p.AddP70bool("LODBox", 0); + p.AddP70bool("Freeze", false); + p.AddP70bool("LODBox", false); p.AddP70( "Lcl Translation", "Lcl Translation", "", "A", double(0), double(0), double(0) @@ -839,9 +839,9 @@ void FBXExporter::WriteDefinitions () p.AddP70color("Color", 0, 0, 0); p.AddP70vector("BBoxMin", 0, 0, 0); p.AddP70vector("BBoxMax", 0, 0, 0); - p.AddP70bool("Primary Visibility", 1); - p.AddP70bool("Casts Shadows", 1); - p.AddP70bool("Receive Shadows", 1); + p.AddP70bool("Primary Visibility", true); + p.AddP70bool("Casts Shadows", true); + p.AddP70bool("Receive Shadows", true); pt.AddChild(p); n.AddChild(pt); object_nodes.push_back(n); @@ -872,7 +872,7 @@ void FBXExporter::WriteDefinitions () } else { p.AddP70string("ShadingModel", "Lambert"); } - p.AddP70bool("MultiLayer", 0); + p.AddP70bool("MultiLayer", false); p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0); p.AddP70numberA("EmissiveFactor", 1.0); p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2); @@ -909,7 +909,7 @@ void FBXExporter::WriteDefinitions () n.AddChild("Count", count); pt = FBX::Node("PropertyTemplate", "FbxVideo"); p = FBX::Node("Properties70"); - p.AddP70bool("ImageSequence", 0); + p.AddP70bool("ImageSequence", false); p.AddP70int("ImageSequenceOffset", 0); p.AddP70double("FrameRate", 0.0); p.AddP70int("LastFrame", 0); @@ -921,8 +921,8 @@ void FBXExporter::WriteDefinitions () p.AddP70double("PlaySpeed", 0.0); p.AddP70time("Offset", 0); p.AddP70enum("InterlaceMode", 0); - p.AddP70bool("FreeRunning", 0); - p.AddP70bool("Loop", 0); + p.AddP70bool("FreeRunning", false); + p.AddP70bool("Loop", false); p.AddP70enum("AccessMode", 0); pt.AddChild(p); n.AddChild(pt); @@ -943,8 +943,8 @@ void FBXExporter::WriteDefinitions () p.AddP70enum("CurrentMappingType", 0); p.AddP70enum("WrapModeU", 0); p.AddP70enum("WrapModeV", 0); - p.AddP70bool("UVSwap", 0); - p.AddP70bool("PremultiplyAlpha", 1); + p.AddP70bool("UVSwap", false); + p.AddP70bool("PremultiplyAlpha", true); p.AddP70vectorA("Translation", 0.0, 0.0, 0.0); p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0); p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0); @@ -952,8 +952,8 @@ void FBXExporter::WriteDefinitions () p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0); p.AddP70enum("CurrentTextureBlendMode", 1); p.AddP70string("UVSet", "default"); - p.AddP70bool("UseMaterial", 0); - p.AddP70bool("UseMipMap", 0); + p.AddP70bool("UseMaterial", false); + p.AddP70bool("UseMipMap", false); pt.AddChild(p); n.AddChild(pt); object_nodes.push_back(n); @@ -1051,7 +1051,7 @@ aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node) aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) { std::vector node_chain; - while (node != scene->mRootNode) { + while (node != scene->mRootNode && node != nullptr) { node_chain.push_back(node); node = node->mParent; } @@ -1062,14 +1062,14 @@ aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) return transform; } -int64_t to_ktime(double ticks, const aiAnimation* anim) { - if (anim->mTicksPerSecond <= 0) { +inline int64_t to_ktime(double ticks, const aiAnimation* anim) { + if (FP_ZERO == std::fpclassify(anim->mTicksPerSecond)) { return static_cast(ticks) * FBX::SECOND; } - return (static_cast(ticks) / static_cast(anim->mTicksPerSecond)) * FBX::SECOND; + return (static_cast(ticks / anim->mTicksPerSecond)) * FBX::SECOND; } -int64_t to_ktime(double time) { +inline int64_t to_ktime(double time) { return (static_cast(time * FBX::SECOND)); } @@ -1089,6 +1089,8 @@ void FBXExporter::WriteObjects () bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true); std::vector> vVertexIndice;//save vertex_indices as it is needed later + const auto bTransparencyFactorReferencedToOpacity = mProperties->GetPropertyBool(AI_CONFIG_EXPORT_FBX_TRANSPARENCY_FACTOR_REFER_TO_OPACITY, false); + // geometry (aiMesh) mesh_uids.clear(); indent = 1; @@ -1215,10 +1217,8 @@ void FBXExporter::WriteObjects () } // colors, if any - // TODO only one color channel currently - const int32_t colorChannelIndex = 0; - if (m->HasVertexColors(colorChannelIndex)) { - FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex)); + for (size_t ci = 0; ci < m->GetNumColorChannels(); ++ci) { + FBX::Node vertexcolors("LayerElementColor", int32_t(ci)); vertexcolors.Begin(outstream, binary, indent); vertexcolors.DumpProperties(outstream, binary, indent); vertexcolors.EndProperties(outstream, binary, indent); @@ -1228,7 +1228,7 @@ void FBXExporter::WriteObjects () "Version", int32_t(101), outstream, binary, indent ); char layerName[8]; - sprintf(layerName, "COLOR_%d", colorChannelIndex); + snprintf(layerName, sizeof(layerName), "COLOR_%d", int32_t(ci)); FBX::Node::WritePropertyNode( "Name", (const char*)layerName, outstream, binary, indent ); @@ -1245,7 +1245,7 @@ void FBXExporter::WriteObjects () for (size_t fi = 0; fi < m->mNumFaces; ++fi) { const aiFace &f = m->mFaces[fi]; for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { - const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]]; + const aiColor4D &c = m->mColors[ci][f.mIndices[pvi]]; color_data.push_back(c.r); color_data.push_back(c.g); color_data.push_back(c.b); @@ -1352,11 +1352,14 @@ void FBXExporter::WriteObjects () le.AddChild("Type", "LayerElementNormal"); le.AddChild("TypedIndex", int32_t(0)); layer.AddChild(le); - // TODO only 1 color channel currently - le = FBX::Node("LayerElement"); - le.AddChild("Type", "LayerElementColor"); - le.AddChild("TypedIndex", int32_t(0)); - layer.AddChild(le); + + for (size_t ci = 0; ci < m->GetNumColorChannels(); ++ci) { + le = FBX::Node("LayerElement"); + le.AddChild("Type", "LayerElementColor"); + le.AddChild("TypedIndex", int32_t(ci)); + layer.AddChild(le); + } + le = FBX::Node("LayerElement"); le.AddChild("Type", "LayerElementMaterial"); le.AddChild("TypedIndex", int32_t(0)); @@ -1390,7 +1393,7 @@ void FBXExporter::WriteObjects () aiMaterial* m = mScene->mMaterials[i]; // these are used to receive material data - float f; aiColor3D c; + ai_real f; aiColor3D c; // start the node record FBX::Node n("Material"); @@ -1445,13 +1448,21 @@ void FBXExporter::WriteObjects () // "TransparentColor" / "TransparencyFactor"... // thanks FBX, for your insightful interpretation of consistency p.AddP70colorA("TransparentColor", c.r, c.g, c.b); - // TransparencyFactor defaults to 0.0, so set it to 1.0. - // note: Maya always sets this to 1.0, - // so we can't use it sensibly as "Opacity". - // In stead we rely on the legacy "Opacity" value, below. - // Blender also relies on "Opacity" not "TransparencyFactor", - // probably for a similar reason. - p.AddP70numberA("TransparencyFactor", 1.0); + + if (!bTransparencyFactorReferencedToOpacity) { + // TransparencyFactor defaults to 0.0, so set it to 1.0. + // note: Maya always sets this to 1.0, + // so we can't use it sensibly as "Opacity". + // In stead we rely on the legacy "Opacity" value, below. + // Blender also relies on "Opacity" not "TransparencyFactor", + // probably for a similar reason. + p.AddP70numberA("TransparencyFactor", 1.0); + } + } + if (bTransparencyFactorReferencedToOpacity) { + if (m->Get(AI_MATKEY_OPACITY, f) == aiReturn_SUCCESS) { + p.AddP70numberA("TransparencyFactor", 1.0 - f); + } } if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) { p.AddP70colorA("ReflectionColor", c.r, c.g, c.b); @@ -1707,7 +1718,7 @@ void FBXExporter::WriteObjects () p.AddP70vectorA("Scaling", trafo.mScaling[0], trafo.mScaling[1], 0.0); p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify //p.AddP70string("UVSet", ""); // TODO: how should this work? - p.AddP70bool("UseMaterial", 1); + p.AddP70bool("UseMaterial", true); tnode.AddChild(p); // can't easily determine which texture path will be correct, // so just store what we have in every field. @@ -1749,7 +1760,7 @@ void FBXExporter::WriteObjects () int64_t blendshape_uid = generate_uid(); mesh_uids.push_back(blendshape_uid); bsnode.AddProperty(blendshape_uid); - bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Blendshape"); + bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Geometry"); bsnode.AddProperty("Shape"); bsnode.AddChild("Version", int32_t(100)); bsnode.Begin(outstream, binary, indent); @@ -1759,23 +1770,25 @@ void FBXExporter::WriteObjects () indent++; if (pAnimMesh->HasPositions()) { std::vectorshape_indices; - std::vectorpPositionDiff; - std::vectorpNormalDiff; + std::vectorpPositionDiff; + std::vectorpNormalDiff; for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) { aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[vt]]); - if(pDiff.Length()>1e-8){ - shape_indices.push_back(vertex_indices[vt]); - pPositionDiff.push_back(pDiff[0]); - pPositionDiff.push_back(pDiff[1]); - pPositionDiff.push_back(pDiff[2]); + shape_indices.push_back(vertex_indices[vt]); + pPositionDiff.push_back(pDiff[0]); + pPositionDiff.push_back(pDiff[1]); + pPositionDiff.push_back(pDiff[2]); - if (pAnimMesh->HasNormals()) { - aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]); - pNormalDiff.push_back(nDiff[0]); - pNormalDiff.push_back(nDiff[1]); - pNormalDiff.push_back(nDiff[2]); - } + if (pAnimMesh->HasNormals()) { + aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]); + pNormalDiff.push_back(nDiff[0]); + pNormalDiff.push_back(nDiff[1]); + pNormalDiff.push_back(nDiff[2]); + } else { + pNormalDiff.push_back(0.0); + pNormalDiff.push_back(0.0); + pNormalDiff.push_back(0.0); } } @@ -1808,7 +1821,7 @@ void FBXExporter::WriteObjects () p.AddP70numberA("DeformPercent", 0.0); sdnode.AddChild(p); // TODO: Normally just one weight per channel, adding stub for later development - std::vectorfFullWeights; + std::vectorfFullWeights; fFullWeights.push_back(100.); sdnode.AddChild("FullWeights", fFullWeights); sdnode.Dump(outstream, binary, indent); @@ -1858,33 +1871,26 @@ void FBXExporter::WriteObjects () // one sticky point is that the number of vertices may not match, // because assimp splits vertices by normal, uv, etc. - // functor for aiNode sorting - struct SortNodeByName - { - bool operator()(const aiNode *lhs, const aiNode *rhs) const - { - return strcmp(lhs->mName.C_Str(), rhs->mName.C_Str()) < 0; - } - }; // first we should mark the skeleton for each mesh. // the skeleton must include not only the aiBones, // but also all their parent nodes. // anything that affects the position of any bone node must be included. - // Use SorNodeByName to make sure the exported result will be the same across all systems - // Otherwise the aiNodes of the skeleton would be sorted based on the pointer address, which isn't consistent - std::vector> skeleton_by_mesh(mScene->mNumMeshes); + + // note that we want to preserve input order as much as possible here. + // previously, sorting by name lead to consistent output across systems, but was not + // suitable for downstream consumption by some applications. + std::vector> skeleton_by_mesh(mScene->mNumMeshes); // at the same time we can build a list of all the skeleton nodes, // which will be used later to mark them as type "limbNode". std::unordered_set limbnodes; //actual bone nodes in fbx, without parenting-up - std::unordered_set setAllBoneNamesInScene; - for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) - { + std::vector allBoneNames; + for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) { aiMesh* pMesh = mScene->mMeshes[m]; for(unsigned int b = 0; b < pMesh->mNumBones; ++ b) - setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data); + allBoneNames.push_back(pMesh->mBones[b]->mName.data); } aiMatrix4x4 mxTransIdentity; @@ -1892,7 +1898,7 @@ void FBXExporter::WriteObjects () std::map node_by_bone; for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { const aiMesh* m = mScene->mMeshes[mi]; - std::set skeleton; + std::vector skeleton; for (size_t bi =0; bi < m->mNumBones; ++bi) { const aiBone* b = m->mBones[bi]; const std::string name(b->mName.C_Str()); @@ -1911,7 +1917,7 @@ void FBXExporter::WriteObjects () node_by_bone[name] = n; limbnodes.insert(n); } - skeleton.insert(n); + skeleton.push_back(n); // mark all parent nodes as skeleton as well, // up until we find the root node, // or else the node containing the mesh, @@ -1922,7 +1928,7 @@ void FBXExporter::WriteObjects () parent = parent->mParent ) { // if we've already done this node we can skip it all - if (skeleton.count(parent)) { + if (std::find(skeleton.begin(), skeleton.end(), parent) != skeleton.end()) { break; } // ignore fbx transform nodes as these will be collapsed later @@ -1932,7 +1938,7 @@ void FBXExporter::WriteObjects () continue; } //not a bone in scene && no effect in transform - if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end() + if (std::find(allBoneNames.begin(), allBoneNames.end(), node_name) == allBoneNames.end() && parent->mTransformation == mxTransIdentity) { continue; } @@ -2017,7 +2023,7 @@ void FBXExporter::WriteObjects () aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene); // now make a subdeformer for each bone in the skeleton - const std::set skeleton= skeleton_by_mesh[mi]; + const auto & skeleton= skeleton_by_mesh[mi]; for (const aiNode* bone_node : skeleton) { // if there's a bone for this node, find it const aiBone* b = nullptr; @@ -2414,7 +2420,7 @@ void FBXExporter::WriteObjects () // position/translation for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) { const aiVectorKey& k = na->mPositionKeys[ki]; - times.push_back(to_ktime(k.mTime)); + times.push_back(to_ktime(k.mTime, anim)); xval.push_back(k.mValue.x); yval.push_back(k.mValue.y); zval.push_back(k.mValue.z); @@ -2428,12 +2434,12 @@ void FBXExporter::WriteObjects () times.clear(); xval.clear(); yval.clear(); zval.clear(); for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) { const aiQuatKey& k = na->mRotationKeys[ki]; - times.push_back(to_ktime(k.mTime)); + times.push_back(to_ktime(k.mTime, anim)); // TODO: aiQuaternion method to convert to Euler... aiMatrix4x4 m(k.mValue.GetMatrix()); aiVector3D qs, qr, qt; m.Decompose(qs, qr, qt); - qr *= DEG; + qr = AI_RAD_TO_DEG(qr); xval.push_back(qr.x); yval.push_back(qr.y); zval.push_back(qr.z); @@ -2446,7 +2452,7 @@ void FBXExporter::WriteObjects () times.clear(); xval.clear(); yval.clear(); zval.clear(); for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) { const aiVectorKey& k = na->mScalingKeys[ki]; - times.push_back(to_ktime(k.mTime)); + times.push_back(to_ktime(k.mTime, anim)); xval.push_back(k.mValue.x); yval.push_back(k.mValue.y); zval.push_back(k.mValue.z); @@ -2483,6 +2489,57 @@ const std::map> transform_types = { {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}} }; +//add metadata to fbx property +void add_meta(FBX::Node& fbx_node, const aiNode* node){ + if(node->mMetaData == nullptr) return; + aiMetadata* meta = node->mMetaData; + for (unsigned int i = 0; i < meta->mNumProperties; ++i) { + aiString key = meta->mKeys[i]; + aiMetadataEntry* entry = &meta->mValues[i]; + switch (entry->mType) { + case AI_BOOL:{ + bool val = *static_cast(entry->mData); + fbx_node.AddP70bool(key.C_Str(), val); + break; + } + case AI_INT32:{ + int32_t val = *static_cast(entry->mData); + fbx_node.AddP70int(key.C_Str(), val); + break; + } + case AI_UINT64:{ + //use string to add uint64 + uint64_t val = *static_cast(entry->mData); + fbx_node.AddP70string(key.C_Str(), std::to_string(val).c_str()); + break; + } + case AI_FLOAT:{ + float val = *static_cast(entry->mData); + fbx_node.AddP70double(key.C_Str(), val); + break; + } + case AI_DOUBLE:{ + double val = *static_cast(entry->mData); + fbx_node.AddP70double(key.C_Str(), val); + break; + } + case AI_AISTRING:{ + aiString val = *static_cast(entry->mData); + fbx_node.AddP70string(key.C_Str(), val.C_Str()); + break; + } + case AI_AIMETADATA: { + //ignore + break; + } + default: + break; + } + + } + +} + // write a single model node to the stream void FBXExporter::WriteModelNode( StreamWriterLE& outstream, @@ -2497,10 +2554,10 @@ void FBXExporter::WriteModelNode( const aiVector3D one = {1, 1, 1}; FBX::Node m("Model"); std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model"; - m.AddProperties(node_uid, name, type); + m.AddProperties(node_uid, std::move(name), type); m.AddChild("Version", int32_t(232)); FBX::Node p("Properties70"); - p.AddP70bool("RotationActive", 1); + p.AddP70bool("RotationActive", true); p.AddP70int("DefaultAttributeIndex", 0); p.AddP70enum("InheritType", inherit_type); if (transform_chain.empty()) { @@ -2514,9 +2571,10 @@ void FBXExporter::WriteModelNode( ); } if (r != zero) { + r = AI_RAD_TO_DEG(r); p.AddP70( "Lcl Rotation", "Lcl Rotation", "", "A", - double(DEG*r.x), double(DEG*r.y), double(DEG*r.z) + double(r.x), double(r.y), double(r.z) ); } if (s != one) { @@ -2549,6 +2607,7 @@ void FBXExporter::WriteModelNode( } } } + add_meta(p, node); m.AddChild(p); // not sure what these are for, @@ -2600,8 +2659,7 @@ void FBXExporter::WriteModelNodes( transform_chain.emplace_back(elem->first, t); break; case 'r': // rotation - r *= float(DEG); - transform_chain.emplace_back(elem->first, r); + transform_chain.emplace_back(elem->first, AI_RAD_TO_DEG(r)); break; case 's': // scale transform_chain.emplace_back(elem->first, s); diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.h index 659f9368a..df9029196 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXImportSettings.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXImportSettings.h index 698901180..bd355bd34 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXImportSettings.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXImportSettings.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -156,6 +156,9 @@ struct ImportSettings { /** Set to true to perform a conversion from cm to meter after the import */ bool convertToMeters; + + // Set to true to ignore the axis configuration in the file + bool ignoreUpDirection = false; }; } // namespace FBX diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.cpp index 7ff194905..491919bdc 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,8 +62,7 @@ namespace Assimp { template <> const char *LogFunctions::Prefix() { - static auto prefix = "FBX: "; - return prefix; + return "FBX: "; } } // namespace Assimp @@ -73,33 +72,25 @@ using namespace Assimp::Formatter; using namespace Assimp::FBX; namespace { - -static const aiImporterDesc desc = { - "Autodesk FBX Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "fbx" -}; -} - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by #Importer -FBXImporter::FBXImporter() : - mSettings() { - // empty + static constexpr aiImporterDesc desc = { + "Autodesk FBX Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "fbx" + }; } // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool FBXImporter::CanRead(const std::string & pFile, IOSystem * pIOHandler, bool /*checkSig*/) const { // at least ASCII-FBX files usually have a 'FBX' somewhere in their head - static const char *tokens[] = { "fbx" }; + static const char *tokens[] = { " \n\r\n " }; return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); } @@ -126,6 +117,7 @@ void FBXImporter::SetupProperties(const Importer *pImp) { mSettings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); mSettings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); mSettings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); + mSettings.ignoreUpDirection = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_IGNORE_UP_DIRECTION, false); mSettings.useSkeleton = pImp->GetPropertyBool(AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER, false); } @@ -156,19 +148,19 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // broad-phase tokenized pass in which we identify the core // syntax elements of FBX (brackets, commas, key:value mappings) TokenList tokens; - try { - + Assimp::StackAllocator tempAllocator; + try { bool is_binary = false; if (!strncmp(begin, "Kaydara FBX Binary", 18)) { is_binary = true; - TokenizeBinary(tokens, begin, contents.size()); + TokenizeBinary(tokens, begin, contents.size(), tempAllocator); } else { - Tokenize(tokens, begin); + Tokenize(tokens, begin, tempAllocator); } // use this information to construct a very rudimentary // parse-tree representing the FBX scope structure - Parser parser(tokens, is_binary); + Parser parser(tokens, tempAllocator, is_binary); // take the raw parse-tree and convert it to a FBX DOM Document doc(parser, mSettings); @@ -187,10 +179,12 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // assimp universal format (M) SetFileScale(size_relative_to_cm * 0.01f); - std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); - } catch (std::exception &) { - std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); - throw; + // This collection does not own the memory for the tokens, but we need to call their d'tor + std::for_each(tokens.begin(), tokens.end(), Util::destructor_fun()); + + } catch (std::exception &) { + std::for_each(tokens.begin(), tokens.end(), Util::destructor_fun()); + throw; } } diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.h index 9acabaddd..8e8a7db78 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -70,7 +70,7 @@ typedef class basic_formatter, std::allocator class FBXImporter : public BaseImporter, public LogFunctions { public: /// @brief The class constructor. - FBXImporter(); + FBXImporter() = default; /// @brief The class destructor, default implementation. ~FBXImporter() override = default; diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXMaterial.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXMaterial.cpp index 0db0ec6d7..3872a4b38 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXMaterial.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXMaterial.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -138,20 +138,6 @@ Material::Material(uint64_t id, const Element& element, const Document& doc, con // ------------------------------------------------------------------------------------------------ Material::~Material() = default; - aiVector2D uvTrans; - aiVector2D uvScaling; - ai_real uvRotation; - - std::string type; - std::string relativeFileName; - std::string fileName; - std::string alphaSource; - std::shared_ptr props; - - unsigned int crop[4]{}; - - const Video* media; - // ------------------------------------------------------------------------------------------------ Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) : Object(id,element,name), @@ -292,10 +278,10 @@ void LayeredTexture::fillTexture(const Document& doc) { } // ------------------------------------------------------------------------------------------------ -Video::Video(uint64_t id, const Element& element, const Document& doc, const std::string& name) : - Object(id,element,name), +Video::Video(uint64_t id, const Element &element, const Document &doc, const std::string &name) : + Object(id, element, name), contentLength(0), - content(0) { + content(nullptr) { const Scope& sc = GetRequiredScope(element); const Element* const Type = sc["Type"]; @@ -380,9 +366,10 @@ Video::Video(uint64_t id, const Element& element, const Document& doc, const std props = GetPropertyTable(doc,"Video.FbxVideo",element,sc); } - Video::~Video() { - delete[] content; + if (contentLength > 0) { + delete[] content; + } } } //!FBX diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp index 58bacfad4..7deaa5be7 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -69,13 +69,16 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, } const BlendShape* const bsp = ProcessSimpleConnection(*con, false, "BlendShape -> Geometry", element); if (bsp) { - blendShapes.push_back(bsp); + auto pr = blendShapes.insert(bsp); + if (!pr.second) { + FBXImporter::LogWarn("there is the same blendShape id ", bsp->ID()); + } } } } // ------------------------------------------------------------------------------------------------ -const std::vector& Geometry::GetBlendShapes() const { +const std::unordered_set& Geometry::GetBlendShapes() const { return blendShapes; } @@ -415,9 +418,11 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, { bool isDirect = ReferenceInformationType == "Direct"; bool isIndexToDirect = ReferenceInformationType == "IndexToDirect"; + const bool hasDataElement = HasElement(source, dataElementName); + const bool hasIndexDataElement = HasElement(source, indexDataElementName); // fall-back to direct data if there is no index data element - if ( isIndexToDirect && !HasElement( source, indexDataElementName ) ) { + if (isIndexToDirect && !hasIndexDataElement) { isDirect = true; isIndexToDirect = false; } @@ -426,7 +431,8 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, // deal with this more elegantly and with less redundancy, but right // now it seems unavoidable. if (MappingInformationType == "ByVertice" && isDirect) { - if (!HasElement(source, dataElementName)) { + if (!hasDataElement) { + FBXImporter::LogWarn("missing data element: ", dataElementName); return; } std::vector tempData; @@ -448,14 +454,22 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, } else if (MappingInformationType == "ByVertice" && isIndexToDirect) { std::vector tempData; - ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + if (!hasDataElement || !hasIndexDataElement) { + if (!hasDataElement) + FBXImporter::LogWarn("missing data element: ", dataElementName); + if (!hasIndexDataElement) + FBXImporter::LogWarn("missing index data element: ", indexDataElementName); + return; + } + + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); std::vector uvIndices; ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); - if (uvIndices.size() != vertex_count) { + if (uvIndices.size() != mapping_offsets.size()) { FBXImporter::LogError("length of input data unexpected for ByVertice mapping: ", - uvIndices.size(), ", expected ", vertex_count); + uvIndices.size(), ", expected ", mapping_offsets.size()); return; } @@ -473,6 +487,11 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, } } else if (MappingInformationType == "ByPolygonVertex" && isDirect) { + if (!hasDataElement) { + FBXImporter::LogWarn("missing data element: ", dataElementName); + return; + } + std::vector tempData; ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); @@ -487,7 +506,14 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, } else if (MappingInformationType == "ByPolygonVertex" && isIndexToDirect) { std::vector tempData; - ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); + if (!hasDataElement || !hasIndexDataElement) { + if (!hasDataElement) + FBXImporter::LogWarn("missing data element: ", dataElementName); + if (!hasIndexDataElement) + FBXImporter::LogWarn("missing index data element: ", indexDataElementName); + return; + } + ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); std::vector uvIndices; ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); @@ -618,10 +644,12 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons return; } - // materials are handled separately. First of all, they are assigned per-face - // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect - // has a slightly different meaning for materials. - ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials")); + if (source["Materials"]) { + // materials are handled separately. First of all, they are assigned per-face + // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect + // has a slightly different meaning for materials. + ParseVectorDataArray(materials_out, GetRequiredElement(source, "Materials")); + } if (MappingInformationType == "AllSame") { // easy - same material for all faces @@ -657,11 +685,14 @@ ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::str DOMError("failed to read Geometry object (class: Shape), no data scope found"); } const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element); - const Element& Normals = GetRequiredElement(*sc, "Normals", &element); const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element); ParseVectorDataArray(m_indices, Indexes); ParseVectorDataArray(m_vertices, Vertices); - ParseVectorDataArray(m_normals, Normals); + + if ((*sc)["Normals"]) { + const Element& Normals = GetRequiredElement(*sc, "Normals", &element); + ParseVectorDataArray(m_normals, Normals); + } } // ------------------------------------------------------------------------------------------------ diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.h index ad24877e4..980d1a334 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXMeshGeometry.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,84 +52,101 @@ namespace Assimp { namespace FBX { /** - * DOM base class for all kinds of FBX geometry + * @brief DOM base class for all kinds of FBX geometry */ class Geometry : public Object { public: /// @brief The class constructor with all parameters. /// @param id The id. - /// @param element - /// @param name - /// @param doc + /// @param element The element instance + /// @param name The name instance + /// @param doc The document instance Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); + + /// @brief The class destructor, default. virtual ~Geometry() = default; - /// Get the Skin attached to this geometry or nullptr + /// @brief Get the Skin attached to this geometry or nullptr. + /// @return The deformer skip instance as a pointer, nullptr if none. const Skin* DeformerSkin() const; - /// Get the BlendShape attached to this geometry or nullptr - const std::vector& GetBlendShapes() const; + /// @brief Get the BlendShape attached to this geometry or nullptr + /// @return The blendshape arrays. + const std::unordered_set& GetBlendShapes() const; private: const Skin* skin; - std::vector blendShapes; + std::unordered_set blendShapes; + }; typedef std::vector MatIndexArray; - /** - * DOM class for FBX geometry of type "Mesh" + * @brief DOM class for FBX geometry of type "Mesh" */ class MeshGeometry : public Geometry { public: - /** The class constructor */ + /// @brief The class constructor + /// @param id The id. + /// @param element The element instance + /// @param name The name instance + /// @param doc The document instance MeshGeometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); - /** The class destructor */ + /// @brief The class destructor, default. virtual ~MeshGeometry() = default; - /** Get a list of all vertex points, non-unique*/ + /// brief Get a vector of all vertex points, non-unique. + /// @return The vertices vector. const std::vector& GetVertices() const; - /** Get a list of all vertex normals or an empty array if - * no normals are specified. */ + /// @brief Get a vector of all vertex normals or an empty array if no normals are specified. + /// @return The normal vector. const std::vector& GetNormals() const; - /** Get a list of all vertex tangents or an empty array - * if no tangents are specified */ + /// @brief Get a vector of all vertex tangents or an empty array if no tangents are specified. + /// @return The vertex tangents vector. const std::vector& GetTangents() const; - /** Get a list of all vertex bi-normals or an empty array - * if no bi-normals are specified */ + /// @brief Get a vector of all vertex bi-normals or an empty array if no bi-normals are specified. + /// @return The binomal vector. const std::vector& GetBinormals() const; - /** Return list of faces - each entry denotes a face and specifies - * how many vertices it has. Vertices are taken from the - * vertex data arrays in sequential order. */ + /// @brief Return list of faces - each entry denotes a face and specifies how many vertices it has. + /// Vertices are taken from the vertex data arrays in sequential order. + /// @return The face indices vector. const std::vector& GetFaceIndexCounts() const; - /** Get a UV coordinate slot, returns an empty array if - * the requested slot does not exist. */ + /// @brief Get a UV coordinate slot, returns an empty array if the requested slot does not exist. + /// @param index The requested texture coordinate slot. + /// @return The texture coordinates. const std::vector& GetTextureCoords( unsigned int index ) const; - /** Get a UV coordinate slot, returns an empty array if - * the requested slot does not exist. */ + /// @brief Get a UV coordinate slot, returns an empty array if the requested slot does not exist. + /// @param index The requested texture coordinate slot. + /// @return The texture coordinate channel name. std::string GetTextureCoordChannelName( unsigned int index ) const; - /** Get a vertex color coordinate slot, returns an empty array if - * the requested slot does not exist. */ + /// @brief Get a vertex color coordinate slot, returns an empty array if the requested slot does not exist. + /// @param index The requested texture coordinate slot. + /// @return The vertex color vector. const std::vector& GetVertexColors( unsigned int index ) const; - /** Get per-face-vertex material assignments */ + /// @brief Get per-face-vertex material assignments. + /// @return The Material indices Array. const MatIndexArray& GetMaterialIndices() const; - /** Convert from a fbx file vertex index (for example from a #Cluster weight) or nullptr - * if the vertex index is not valid. */ + /// @brief Convert from a fbx file vertex index (for example from a #Cluster weight) or nullptr if the vertex index is not valid. + /// @param in_index The requested input index. + /// @param count The number of indices. + /// @return The indices. const unsigned int* ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const; - /** Determine the face to which a particular output vertex index belongs. - * This mapping is always unique. */ + /// @brief Determine the face to which a particular output vertex index belongs. + /// This mapping is always unique. + /// @param in_index The requested input index. + /// @return The face-to-vertex index. unsigned int FaceForVertexIndex( unsigned int in_index ) const; private: diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXModel.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXModel.cpp index d731d2e29..c108dd78b 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXModel.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXModel.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp index 34fcdcf77..1e7dfa8c0 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXNodeAttribute.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.cpp index da6d3889a..d0482e067 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -45,12 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER -//#ifdef ASSIMP_BUILD_NO_OWN_ZLIB #include "Common/Compression.h" -//# include -//#else -//# include "../contrib/zlib/zlib.h" -//#endif #include "FBXTokenizer.h" #include "FBXParser.h" @@ -88,6 +83,7 @@ namespace { // ------------------------------------------------------------------------------------------------ + AI_WONT_RETURN void ParseError(const std::string& message, TokenPtr token) AI_WONT_RETURN_SUFFIX; void ParseError(const std::string& message, TokenPtr token) { if(token) { @@ -115,8 +111,11 @@ namespace Assimp { namespace FBX { // ------------------------------------------------------------------------------------------------ -Element::Element(const Token& key_token, Parser& parser) : key_token(key_token) { +Element::Element(const Token& key_token, Parser& parser) : + key_token(key_token), compound(nullptr) +{ TokenPtr n = nullptr; + StackAllocator &allocator = parser.GetAllocator(); do { n = parser.AdvanceToNextToken(); if(!n) { @@ -145,7 +144,7 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token) } if (n->Type() == TokenType_OPEN_BRACKET) { - compound.reset(new Scope(parser)); + compound = new_Scope(parser); // current token should be a TOK_CLOSE_BRACKET n = parser.CurrentToken(); @@ -163,6 +162,15 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token) } // ------------------------------------------------------------------------------------------------ +Element::~Element() +{ + if (compound) { + delete_Scope(compound); + } + + // no need to delete tokens, they are owned by the parser +} + Scope::Scope(Parser& parser,bool topLevel) { if(!topLevel) { @@ -172,6 +180,7 @@ Scope::Scope(Parser& parser,bool topLevel) } } + StackAllocator &allocator = parser.GetAllocator(); TokenPtr n = parser.AdvanceToNextToken(); if (n == nullptr) { ParseError("unexpected end of file"); @@ -187,37 +196,46 @@ Scope::Scope(Parser& parser,bool topLevel) if (str.empty()) { ParseError("unexpected content: empty string."); } - - elements.insert(ElementMap::value_type(str,new_Element(*n,parser))); + + auto *element = new_Element(*n, parser); // Element() should stop at the next Key token (or right after a Close token) n = parser.CurrentToken(); if (n == nullptr) { if (topLevel) { + elements.insert(ElementMap::value_type(str, element)); return; } + delete_Element(element); ParseError("unexpected end of file",parser.LastToken()); + } else { + elements.insert(ElementMap::value_type(str, element)); } } } // ------------------------------------------------------------------------------------------------ -Scope::~Scope() { - for(ElementMap::value_type& v : elements) { - delete v.second; +Scope::~Scope() +{ + // This collection does not own the memory for the elements, but we need to call their d'tor: + + for (ElementMap::value_type &v : elements) { + delete_Element(v.second); } } // ------------------------------------------------------------------------------------------------ -Parser::Parser (const TokenList& tokens, bool is_binary) -: tokens(tokens) -, last() -, current() -, cursor(tokens.begin()) -, is_binary(is_binary) +Parser::Parser(const TokenList &tokens, StackAllocator &allocator, bool is_binary) : + tokens(tokens), allocator(allocator), last(), current(), cursor(tokens.begin()), is_binary(is_binary) { ASSIMP_LOG_DEBUG("Parsing FBX tokens"); - root.reset(new Scope(*this,true)); + root = new_Scope(*this, true); +} + +// ------------------------------------------------------------------------------------------------ +Parser::~Parser() +{ + delete_Scope(root); } // ------------------------------------------------------------------------------------------------ diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.h index 6aeedb211..2ca216d8c 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include "Common/StackAllocator.h" #include "FBXCompileConfig.h" #include "FBXTokenizer.h" @@ -62,15 +63,14 @@ class Scope; class Parser; class Element; -// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03 -typedef std::vector< Scope* > ScopeList; -typedef std::fbx_unordered_multimap< std::string, Element* > ElementMap; - -typedef std::pair ElementCollection; - -# define new_Scope new Scope -# define new_Element new Element +using ScopeList = std::vector; +using ElementMap = std::fbx_unordered_multimap< std::string, Element*>; +using ElementCollection = std::pair; +#define new_Scope new (allocator.Allocate(sizeof(Scope))) Scope +#define new_Element new (allocator.Allocate(sizeof(Element))) Element +#define delete_Scope(_p) (_p)->~Scope() +#define delete_Element(_p) (_p)->~Element() /** FBX data entity that consists of a key:value tuple. * @@ -82,15 +82,16 @@ typedef std::pair Element * @endverbatim * * As can be seen in this sample, elements can contain nested #Scope - * as their trailing member. **/ + * as their trailing member. +**/ class Element { public: Element(const Token& key_token, Parser& parser); - ~Element() = default; + ~Element(); const Scope* Compound() const { - return compound.get(); + return compound; } const Token& KeyToken() const { @@ -104,7 +105,7 @@ public: private: const Token& key_token; TokenList tokens; - std::unique_ptr compound; + Scope* compound; }; /** FBX data entity that consists of a 'scope', a collection @@ -133,7 +134,7 @@ public: const char* elementNameCStr = elementName.c_str(); for (auto element = elements.begin(); element != elements.end(); ++element) { - if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, MAXLEN)) { + if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, AI_MAXLEN)) { return element->second; } } @@ -159,17 +160,21 @@ class Parser public: /** Parse given a token list. Does not take ownership of the tokens - * the objects must persist during the entire parser lifetime */ - Parser (const TokenList& tokens,bool is_binary); - ~Parser() = default; + Parser(const TokenList &tokens, StackAllocator &allocator, bool is_binary); + ~Parser(); const Scope& GetRootScope() const { - return *root.get(); + return *root; } bool IsBinary() const { return is_binary; } + StackAllocator &GetAllocator() { + return allocator; + } + private: friend class Scope; friend class Element; @@ -180,10 +185,10 @@ private: private: const TokenList& tokens; - + StackAllocator &allocator; TokenPtr last, current; TokenList::const_iterator cursor; - std::unique_ptr root; + Scope *root; const bool is_binary; }; diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.cpp index 1c050617d..6f577d09f 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -243,7 +243,6 @@ DirectPropertyMap PropertyTable::GetUnparsedProperties() const // Read the element's value. // Wrap the naked pointer (since the call site is required to acquire ownership) - // std::unique_ptr from C++11 would be preferred both as a wrapper and a return value. std::shared_ptr prop = std::shared_ptr(ReadTypedProperty(*currentElement.second)); // Element could not be read. Skip it. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.h index 18816117a..4799b8056 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXProperties.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.cpp index f63e687e8..007e08d46 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -94,7 +94,8 @@ AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line, // process a potential data token up to 'cur', adding it to 'output_tokens'. // ------------------------------------------------------------------------------------------------ -void ProcessDataToken( TokenList& output_tokens, const char*& start, const char*& end, +void ProcessDataToken(TokenList &output_tokens, StackAllocator &token_allocator, + const char*& start, const char*& end, unsigned int line, unsigned int column, TokenType type = TokenType_DATA, @@ -131,8 +132,7 @@ void ProcessDataToken( TokenList& output_tokens, const char*& start, const char* } // ------------------------------------------------------------------------------------------------ -void Tokenize(TokenList& output_tokens, const char* input) -{ +void Tokenize(TokenList &output_tokens, const char *input, StackAllocator &token_allocator) { ai_assert(input); ASSIMP_LOG_DEBUG("Tokenizing ASCII FBX file"); @@ -164,7 +164,7 @@ void Tokenize(TokenList& output_tokens, const char* input) in_double_quotes = false; token_end = cur; - ProcessDataToken(output_tokens,token_begin,token_end,line,column); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column); pending_data_token = false; } continue; @@ -181,30 +181,30 @@ void Tokenize(TokenList& output_tokens, const char* input) continue; case ';': - ProcessDataToken(output_tokens,token_begin,token_end,line,column); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column); comment = true; continue; case '{': - ProcessDataToken(output_tokens,token_begin,token_end, line, column); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column); output_tokens.push_back(new_Token(cur,cur+1,TokenType_OPEN_BRACKET,line,column)); continue; case '}': - ProcessDataToken(output_tokens,token_begin,token_end,line,column); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column); output_tokens.push_back(new_Token(cur,cur+1,TokenType_CLOSE_BRACKET,line,column)); continue; case ',': if (pending_data_token) { - ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_DATA,true); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, TokenType_DATA, true); } output_tokens.push_back(new_Token(cur,cur+1,TokenType_COMMA,line,column)); continue; case ':': if (pending_data_token) { - ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_KEY,true); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, TokenType_KEY, true); } else { TokenizeError("unexpected colon", line, column); @@ -226,7 +226,7 @@ void Tokenize(TokenList& output_tokens, const char* input) } } - ProcessDataToken(output_tokens,token_begin,token_end,line,column,type); + ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, type); } pending_data_token = false; diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.h index 5ed48e61d..dedfab66a 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXTokenizer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_TOKENIZER_H #include "FBXCompileConfig.h" +#include "Common/StackAllocator.h" #include #include #include @@ -154,11 +155,11 @@ private: const unsigned int column; }; -// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03 typedef const Token* TokenPtr; typedef std::vector< TokenPtr > TokenList; -#define new_Token new Token +#define new_Token new (token_allocator.Allocate(sizeof(Token))) Token +#define delete_Token(_p) (_p)->~Token() /** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens. @@ -168,7 +169,7 @@ typedef std::vector< TokenPtr > TokenList; * @param output_tokens Receives a list of all tokens in the input data. * @param input_buffer Textual input buffer to be processed, 0-terminated. * @throw DeadlyImportError if something goes wrong */ -void Tokenize(TokenList& output_tokens, const char* input); +void Tokenize(TokenList &output_tokens, const char *input, StackAllocator &tokenAllocator); /** Tokenizer function for binary FBX files. @@ -179,7 +180,7 @@ void Tokenize(TokenList& output_tokens, const char* input); * @param input_buffer Binary input buffer to be processed. * @param length Length of input buffer, in bytes. There is no 0-terminal. * @throw DeadlyImportError if something goes wrong */ -void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length); +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, StackAllocator &tokenAllocator); } // ! FBX diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.cpp b/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.cpp index ac465d6e9..a787a9f1d 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.cpp +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -155,7 +155,7 @@ size_t DecodeBase64(const char* in, size_t inLength, uint8_t* out, size_t maxOut const size_t realLength = inLength - size_t(in[inLength - 1] == '=') - size_t(in[inLength - 2] == '='); size_t dst_offset = 0; int val = 0, valb = -8; - for (size_t src_offset = 0; src_offset < realLength; ++src_offset) + for (size_t src_offset = 0; src_offset < realLength && dst_offset < maxOutLength; ++src_offset) { const uint8_t table_value = Util::DecodeBase64(in[src_offset]); if (table_value == 255) diff --git a/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.h b/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.h index 0e0bb75be..eb9ae14ed 100644 --- a/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.h +++ b/Engine/lib/assimp/code/AssetLib/FBX/FBXUtil.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -66,6 +66,17 @@ struct delete_fun } }; +/** helper for std::for_each to call the destructor on all items in a container without freeing their heap*/ +template +struct destructor_fun { + void operator()(const volatile T* del) { + if (del) { + del->~T(); + } + } +}; + + /** Get a string representation for a #TokenType. */ const char* TokenTypeString(TokenType t); diff --git a/Engine/lib/assimp/code/AssetLib/HMP/HMPFileData.h b/Engine/lib/assimp/code/AssetLib/HMP/HMPFileData.h index b297136ba..5f6ca4f55 100644 --- a/Engine/lib/assimp/code/AssetLib/HMP/HMPFileData.h +++ b/Engine/lib/assimp/code/AssetLib/HMP/HMPFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -49,7 +49,7 @@ namespace HMP { #include #include -// to make it easier for us, we test the magic word against both "endianesses" +// to make it easier for us, we test the magic word against both "endiannesses" #define AI_HMP_MAGIC_NUMBER_BE_4 AI_MAKE_MAGIC("HMP4") #define AI_HMP_MAGIC_NUMBER_LE_4 AI_MAKE_MAGIC("4PMH") diff --git a/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.cpp b/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.cpp index 79fdae807..30931a920 100644 --- a/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,7 +57,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "3D GameStudio Heightmap (HMP) Importer", "", "", @@ -104,7 +104,7 @@ void HMPImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(mIOHandler->Open(pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open HMP file ", pFile, "."); } @@ -115,7 +115,9 @@ void HMPImporter::InternReadFile(const std::string &pFile, throw DeadlyImportError("HMP File is too small."); // Allocate storage and copy the contents of the file to a memory buffer - mBuffer = new uint8_t[fileSize]; + auto deleter=[this](uint8_t* ptr){ delete[] ptr; mBuffer = nullptr; }; + std::unique_ptr buffer(new uint8_t[fileSize], deleter); + mBuffer = buffer.get(); file->Read((void *)mBuffer, 1, fileSize); iFileSize = (unsigned int)fileSize; @@ -143,9 +145,6 @@ void HMPImporter::InternReadFile(const std::string &pFile, // Print the magic word to the logger std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic)); - delete[] mBuffer; - mBuffer = nullptr; - // We're definitely unable to load this file throw DeadlyImportError("Unknown HMP subformat ", pFile, ". Magic word (", szBuffer, ") is not known"); @@ -153,9 +152,6 @@ void HMPImporter::InternReadFile(const std::string &pFile, // Set the AI_SCENE_FLAGS_TERRAIN bit pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN; - - delete[] mBuffer; - mBuffer = nullptr; } // ------------------------------------------------------------------------------------------------ @@ -327,7 +323,7 @@ void HMPImporter::CreateMaterial(const unsigned char *szCurrent, ReadFirstSkin(pcHeader->numskins, szCurrent, &szCurrent); *szCurrentOut = szCurrent; return; - } + } // generate a default material const int iMode = (int)aiShadingMode_Gouraud; @@ -445,11 +441,11 @@ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szC szCursor += sizeof(uint32_t); // allocate an output material - aiMaterial *pcMat = new aiMaterial(); + std::unique_ptr pcMat(new aiMaterial()); // read the skin, this works exactly as for MDL7 ParseSkinLump_3DGS_MDL7(szCursor, &szCursor, - pcMat, iType, iWidth, iHeight); + pcMat.get(), iType, iWidth, iHeight); // now we need to skip any other skins ... for (unsigned int i = 1; i < iNumSkins; ++i) { @@ -468,7 +464,7 @@ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szC // setup the material ... pScene->mNumMaterials = 1; pScene->mMaterials = new aiMaterial *[1]; - pScene->mMaterials[0] = pcMat; + pScene->mMaterials[0] = pcMat.release(); *szCursorOut = szCursor; } @@ -484,11 +480,11 @@ void HMPImporter::GenerateTextureCoords(const unsigned int width, const unsigned if (uv == nullptr) { return; } - + if (height == 0.0f || width == 0.0) { return; } - + const float fY = (1.0f / height) + (1.0f / height) / height; const float fX = (1.0f / width) + (1.0f / width) / width; diff --git a/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.h b/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.h index 95ce0a9eb..e665b8d18 100644 --- a/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.h +++ b/Engine/lib/assimp/code/AssetLib/HMP/HMPLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -86,7 +86,7 @@ protected: // ------------------------------------------------------------------- /** Import a HMP4 file */ - void InternReadFile_HMP4(); + AI_WONT_RETURN void InternReadFile_HMP4() AI_WONT_RETURN_SUFFIX; // ------------------------------------------------------------------- /** Import a HMP5 file diff --git a/Engine/lib/assimp/code/AssetLib/HMP/HalfLifeFileData.h b/Engine/lib/assimp/code/AssetLib/HMP/HalfLifeFileData.h index f47862b7a..687b6108c 100644 --- a/Engine/lib/assimp/code/AssetLib/HMP/HalfLifeFileData.h +++ b/Engine/lib/assimp/code/AssetLib/HMP/HalfLifeFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCBoolean.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCBoolean.cpp index 2a8456719..559bd7b2f 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCBoolean.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCBoolean.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -38,9 +38,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCBoolean.cpp - * @brief Implements a subset of Ifc boolean operations - */ +/// @file IFCBoolean.cpp +/// @brief Implements a subset of Ifc boolean operations #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER @@ -48,9 +47,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Common/PolyTools.h" #include "PostProcessing/ProcessHelper.h" - #include #include +#include namespace Assimp { namespace IFC { @@ -66,8 +65,9 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe // if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this // point leaves the plane through the other side - if (std::abs(dotOne + dotTwo) < ai_epsilon) + if (std::abs(dotOne + dotTwo) < ai_epsilon) { return false; + } // if segment starts on the plane, report a hit only if the end lies on the *other* side if (std::abs(dotTwo) < ai_epsilon) { @@ -81,13 +81,15 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe // ignore if segment is parallel to plane and far away from it on either side // Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered - if (std::abs(dotOne) < ai_epsilon) + if (std::abs(dotOne) < ai_epsilon) { return false; + } // t must be in [0..1] if the intersection point is within the given segment const IfcFloat t = dotTwo / dotOne; - if (t > 1.0 || t < 0.0) + if (t > 1.0 || t < 0.0) { return false; + } out = e0 + t * seg; return true; @@ -109,11 +111,13 @@ void FilterPolygon(std::vector &resultpoly) { FuzzyVectorCompare fz(epsilon); std::vector::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz); - if (e != resultpoly.end()) + if (e != resultpoly.end()) { resultpoly.erase(e, resultpoly.end()); + } - if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back())) + if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back())) { resultpoly.pop_back(); + } } // ------------------------------------------------------------------------------------------------ @@ -290,8 +294,9 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const } // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments - if (endsAtSegment && !halfOpen) + if (endsAtSegment && !halfOpen) { continue; + } // Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE // state. This should catch the case where a connected set of segments has a point directly on the boundary, @@ -300,15 +305,17 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const if (startsAtSegment) { IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder; bool isGoingInside = (inside_dir * e) > 0.0; - if (isGoingInside == isStartAssumedInside) + if (isGoingInside == isStartAssumedInside) { continue; + } // only insert the point into the list if it is sufficiently far away from the previous intersection point. // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments. if (!intersect_results.empty() && intersect_results.back().first == i - 1) { const IfcVector3 diff = intersect_results.back().second - e0; - if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) + if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) { continue; + } } intersect_results.emplace_back(i, e0); continue; @@ -321,8 +328,9 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments. if (!intersect_results.empty() && intersect_results.back().first == i - 1) { const IfcVector3 diff = intersect_results.back().second - p; - if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) + if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) { continue; + } } intersect_results.emplace_back(i, p); } @@ -387,8 +395,8 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly n.Normalize(); // obtain the polygonal bounding volume - std::shared_ptr profile = std::shared_ptr(new TempMesh()); - if (!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) { + std::shared_ptr profile = std::make_shared(); + if (!ProcessCurve(hs->PolygonalBoundary, *profile, conv)) { IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace"); return; } @@ -661,7 +669,8 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly } // ------------------------------------------------------------------------------------------------ -void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, TempMesh &result, +void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, + TempMesh &result, const TempMesh &first_operand, ConversionData &conv) { ai_assert(as != nullptr); @@ -671,10 +680,10 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea // operand should be near-planar. Luckily, this is usually the case in Ifc // buildings. - std::shared_ptr meshtmp = std::shared_ptr(new TempMesh()); + std::shared_ptr meshtmp = std::make_shared(); ProcessExtrudedAreaSolid(*as, *meshtmp, conv, false); - std::vector openings(1, TempOpening(as, IfcVector3(0, 0, 0), meshtmp, std::shared_ptr())); + std::vector openings(1, TempOpening(as, IfcVector3(0, 0, 0), std::move(meshtmp), std::shared_ptr())); result = first_operand; @@ -762,4 +771,4 @@ void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &resul } // namespace IFC } // namespace Assimp -#endif +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCCurve.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCCurve.cpp index 59774eb11..847803dfa 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCCurve.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCCurve.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -39,15 +39,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCProfile.cpp - * @brief Read profile and curves entities from IFC files - */ +/// @file IFCProfile.cpp +/// @brief Read profile and curves entities from IFC files #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER #include "IFCUtil.h" namespace Assimp { namespace IFC { + namespace { // -------------------------------------------------------------------------------- @@ -56,8 +56,7 @@ namespace { class Conic : public Curve { public: // -------------------------------------------------- - Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv) - : Curve(entity,conv) { + Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv) : Curve(entity,conv) { IfcMatrix4 trafo; ConvertAxisPlacement(trafo,*entity.Position,conv); @@ -69,12 +68,12 @@ public: } // -------------------------------------------------- - bool IsClosed() const { + bool IsClosed() const override { return true; } // -------------------------------------------------- - size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); @@ -88,7 +87,7 @@ public: } // -------------------------------------------------- - ParamRange GetParametricRange() const { + ParamRange GetParametricRange() const override { return std::make_pair(static_cast( 0. ), static_cast( AI_MATH_TWO_PI / conv.angle_scale )); } @@ -102,14 +101,13 @@ protected: class Circle : public Conic { public: // -------------------------------------------------- - Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv) - : Conic(entity,conv) - , entity(entity) - { - } - + Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv) : Conic(entity,conv) , entity(entity) {} + // -------------------------------------------------- - IfcVector3 Eval(IfcFloat u) const { + ~Circle() override = default; + + // -------------------------------------------------- + IfcVector3 Eval(IfcFloat u) const override { u = -conv.angle_scale * u; return location + static_cast(entity.Radius)*(static_cast(std::cos(u))*p[0] + static_cast(std::sin(u))*p[1]); @@ -132,7 +130,7 @@ public: } // -------------------------------------------------- - IfcVector3 Eval(IfcFloat u) const { + IfcVector3 Eval(IfcFloat u) const override { u = -conv.angle_scale * u; return location + static_cast(entity.SemiAxis1)*static_cast(std::cos(u))*p[0] + static_cast(entity.SemiAxis2)*static_cast(std::sin(u))*p[1]; @@ -155,17 +153,17 @@ public: } // -------------------------------------------------- - bool IsClosed() const { + bool IsClosed() const override { return false; } // -------------------------------------------------- - IfcVector3 Eval(IfcFloat u) const { + IfcVector3 Eval(IfcFloat u) const override { return p + u*v; } // -------------------------------------------------- - size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); // two points are always sufficient for a line segment @@ -174,7 +172,7 @@ public: // -------------------------------------------------- - void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const { + void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); @@ -188,7 +186,7 @@ public: } // -------------------------------------------------- - ParamRange GetParametricRange() const { + ParamRange GetParametricRange() const override { const IfcFloat inf = std::numeric_limits::infinity(); return std::make_pair(-inf,+inf); @@ -234,7 +232,7 @@ public: } // -------------------------------------------------- - IfcVector3 Eval(IfcFloat u) const { + IfcVector3 Eval(IfcFloat u) const override { if (curves.empty()) { return IfcVector3(); } @@ -254,7 +252,7 @@ public: } // -------------------------------------------------- - size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); size_t cnt = 0; @@ -275,7 +273,7 @@ public: } // -------------------------------------------------- - void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const { + void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); @@ -293,7 +291,7 @@ public: } // -------------------------------------------------- - ParamRange GetParametricRange() const { + ParamRange GetParametricRange() const override { return std::make_pair(static_cast( 0. ),total); } @@ -373,27 +371,27 @@ public: } // -------------------------------------------------- - IfcVector3 Eval(IfcFloat p) const { + IfcVector3 Eval(IfcFloat p) const override { ai_assert(InRange(p)); return base->Eval( TrimParam(p) ); } // -------------------------------------------------- - size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); return base->EstimateSampleCount(TrimParam(a),TrimParam(b)); } // -------------------------------------------------- - void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const { + void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const override { ai_assert(InRange(a)); ai_assert(InRange(b)); return base->SampleDiscrete(out,TrimParam(a),TrimParam(b)); } // -------------------------------------------------- - ParamRange GetParametricRange() const { + ParamRange GetParametricRange() const override { return std::make_pair(static_cast( 0. ),maxval); } @@ -431,7 +429,7 @@ public: } // -------------------------------------------------- - IfcVector3 Eval(IfcFloat p) const { + IfcVector3 Eval(IfcFloat p) const override { ai_assert(InRange(p)); const size_t b = static_cast(std::floor(p)); @@ -444,14 +442,14 @@ public: } // -------------------------------------------------- - size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const { + size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override { ai_assert(InRange(a)); ai_assert(InRange(b)); return static_cast( std::ceil(b) - std::floor(a) ); } // -------------------------------------------------- - ParamRange GetParametricRange() const { + ParamRange GetParametricRange() const override { return std::make_pair(static_cast( 0. ),static_cast(points.size()-1)); } @@ -516,7 +514,7 @@ size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const { ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); - // arbitrary default value, deriving classes should supply better suited values + // arbitrary default value, deriving classes should supply better-suited values return 16; } diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCGeometry.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCGeometry.cpp index b3620fe81..d488b2376 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCGeometry.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCGeometry.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -38,34 +38,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCGeometry.cpp - * @brief Geometry conversion and synthesis for IFC - */ - - +/// @file IFCGeometry.cpp +/// @brief Geometry conversion and synthesis for IFC #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER #include "IFCUtil.h" #include "Common/PolyTools.h" #include "PostProcessing/ProcessHelper.h" +#include "contrib/poly2tri/poly2tri/poly2tri.h" +#include "contrib/clipper/clipper.hpp" -#ifdef ASSIMP_USE_HUNTER -# include -# include -#else -# include "../contrib/poly2tri/poly2tri/poly2tri.h" -# include "../contrib/clipper/clipper.hpp" -#endif - -#include #include +#include +#include namespace Assimp { namespace IFC { // ------------------------------------------------------------------------------------------------ -bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) -{ +bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) { size_t cnt = 0; for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) { IfcVector3 tmp; @@ -90,8 +81,7 @@ bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, Con } // ------------------------------------------------------------------------------------------------ -void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) -{ +void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) { // handle all trivial cases if(inmesh.mVertcnt.empty()) { return; @@ -126,8 +116,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m if (master_bounds != (size_t)-1) { ai_assert(master_bounds < inmesh.mVertcnt.size()); outer_polygon_it = begin + master_bounds; - } - else { + } else { for(iit = begin; iit != end; ++iit) { // find the polygon with the largest area and take it as the outer bound. IfcVector3& n = normals[std::distance(begin,iit)]; @@ -138,7 +127,8 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m } } } - if (outer_polygon_it == end) { + + if (outer_polygon_it == end) { return; } @@ -204,40 +194,20 @@ void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMe if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr()) { if(ProcessPolyloop(*polyloop, meshout,conv)) { - // The outer boundary is better determined by checking which // polygon covers the largest area. - - //if(bound.ToPtr()) { - // ob = cnt; - //} - //++cnt; - } - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName()); continue; } - - // And this, even though it is sometimes TRUE and sometimes FALSE, - // does not really improve results. - - /*if(!IsTrue(bound.Orientation)) { - size_t c = 0; - for(unsigned int& c : meshout.vertcnt) { - std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c); - cnt += c; - } - }*/ } ProcessPolygonBoundaries(result, meshout); } } // ------------------------------------------------------------------------------------------------ -void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) -{ +void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) { TempMesh meshout; // first read the profile description @@ -264,7 +234,8 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem return; } - const unsigned int cnt_segments = std::max(2u,static_cast(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F)); + const unsigned int cnt_segments = + std::max(2u,static_cast(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F)); const IfcFloat delta = max_angle/cnt_segments; has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99; @@ -323,8 +294,9 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem } // ------------------------------------------------------------------------------------------------ -void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv) -{ +void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, + TempMesh& result, + ConversionData& conv) { const Curve* const curve = Curve::Convert(*solid.Directrix, conv); if(!curve) { IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)"); @@ -459,8 +431,7 @@ void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& } // ------------------------------------------------------------------------------------------------ -IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) -{ +IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) { const std::vector& out = curmesh.mVerts; IfcMatrix3 m; @@ -503,10 +474,6 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect IfcVector3 r = (out[idx]-any_point); r.Normalize(); - //if(d) { - // *d = -any_point * nor; - //} - // Reconstruct orthonormal basis // XXX use Gram Schmidt for increased robustness IfcVector3 u = r ^ nor; @@ -530,8 +497,7 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect const auto closeDistance = ai_epsilon; bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) { - if(pt1.Coordinates.size() != pt2.Coordinates.size()) - { + if(pt1.Coordinates.size() != pt2.Coordinates.size()) { IFCImporter::LogWarn("unable to compare differently-dimensioned points"); return false; } @@ -539,10 +505,10 @@ bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt auto coord2 = pt2.Coordinates.begin(); // we're just testing each dimension separately rather than doing euclidean distance, as we're // looking for very close coordinates - for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) - { - if(std::fabs(*coord1 - *coord2) > closeDistance) + for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) { + if(std::fabs(*coord1 - *coord2) > closeDistance) { return false; + } } return true; } @@ -552,6 +518,7 @@ bool areClose(IfcVector3 pt1,IfcVector3 pt2) { std::fabs(pt1.y - pt2.y) < closeDistance && std::fabs(pt1.z - pt2.z) < closeDistance); } + // Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary. void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve, const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings) @@ -589,8 +556,9 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te // reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size()); - if( profileNormal * dir < 0.0 ) + if( profileNormal * dir < 0.0 ) { std::reverse(in.begin(), in.end()); + } std::vector nors; const bool openings = !!conv.apply_openings && conv.apply_openings->size(); @@ -609,7 +577,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te nors.reserve(conv.apply_openings->size()); for(TempOpening& t : *conv.apply_openings) { - TempMesh& bounds = *t.profileMesh.get(); + TempMesh &bounds = *t.profileMesh; if( bounds.mVerts.size() <= 2 ) { nors.emplace_back(); @@ -677,8 +645,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te if(n > 0) { for(size_t i = 0; i < in.size(); ++i) out.push_back(in[i] + dir); - } - else { + } else { for(size_t i = in.size(); i--; ) out.push_back(in[i]); } @@ -713,16 +680,17 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te std::shared_ptr profile2D = std::shared_ptr(new TempMesh()); profile2D->mVerts.insert(profile2D->mVerts.end(), in.begin(), in.end()); profile2D->mVertcnt.push_back(static_cast(in.size())); - conv.collect_openings->push_back(TempOpening(&solid, dir, profile, profile2D)); + conv.collect_openings->push_back(TempOpening(&solid, dir, std::move(profile), std::move(profile2D))); ai_assert(result.IsEmpty()); } } // ------------------------------------------------------------------------------------------------ -void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result, - ConversionData& conv, bool collect_openings) -{ +void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, + TempMesh& result, + ConversionData& conv, + bool collect_openings) { TempMesh meshout; // First read the profile description. @@ -760,24 +728,23 @@ void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, Tem } // ------------------------------------------------------------------------------------------------ -void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout, - ConversionData& conv) -{ +void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, + TempMesh& meshout, + ConversionData& conv) { if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr()) { ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings); - } - else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr()) { + } else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr()) { ProcessRevolvedAreaSolid(*rev,meshout,conv); - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName()); } } // ------------------------------------------------------------------------------------------------ -bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set& mesh_indices, - ConversionData& conv) -{ +bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, + unsigned int matid, + std::set& mesh_indices, + ConversionData& conv) { bool fix_orientation = false; std::shared_ptr< TempMesh > meshtmp = std::make_shared(); if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { @@ -786,42 +753,33 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>(); const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); - ProcessConnectedFaceSet(fs,*meshtmp.get(),conv); - } - catch(std::bad_cast&) { + ProcessConnectedFaceSet(fs, *meshtmp, conv); + } catch(std::bad_cast&) { IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet"); } } fix_orientation = true; - } - else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr()) { - ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv); + } else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr()) { + ProcessConnectedFaceSet(*fset, *meshtmp, conv); fix_orientation = true; - } - else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr()) { - ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv); - } - else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr()) { - ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv); - } - else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr()) { - ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv); + } else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr()) { + ProcessSweptAreaSolid(*swept, *meshtmp, conv); + } else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr()) { + ProcessSweptDiskSolid(*disk, *meshtmp, conv); + } else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr()) { + ProcessConnectedFaceSet(brep->Outer, *meshtmp, conv); fix_orientation = true; - } - else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { + } else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) { - ProcessConnectedFaceSet(fc,*meshtmp.get(),conv); + ProcessConnectedFaceSet(fc, *meshtmp, conv); } fix_orientation = true; - } - else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr()) { - ProcessBoolean(*boolean,*meshtmp.get(),conv); - } - else if(geo.ToPtr()) { + } else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr()) { + ProcessBoolean(*boolean, *meshtmp, conv); + } else if(geo.ToPtr()) { // silently skip over bounding boxes return false; - } - else { + } else { std::stringstream toLog; toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID(); IFCImporter::LogWarn(toLog.str().c_str()); @@ -839,7 +797,7 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned if (!meshtmp->IsEmpty()) { conv.collect_openings->push_back(TempOpening(geo.ToPtr(), IfcVector3(0,0,0), - meshtmp, + std::move(meshtmp), std::shared_ptr())); } return true; @@ -867,9 +825,7 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned } // ------------------------------------------------------------------------------------------------ -void AssignAddedMeshes(std::set& mesh_indices,aiNode* nd, - ConversionData& /*conv*/) -{ +void AssignAddedMeshes(std::set& mesh_indices,aiNode* nd, ConversionData& /*conv*/) { if (!mesh_indices.empty()) { std::set::const_iterator it = mesh_indices.cbegin(); std::set::const_iterator end = mesh_indices.cend(); @@ -885,9 +841,9 @@ void AssignAddedMeshes(std::set& mesh_indices,aiNode* nd, // ------------------------------------------------------------------------------------------------ bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item, - std::set& mesh_indices, unsigned int mat_index, - ConversionData& conv) -{ + std::set& mesh_indices, + unsigned int mat_index, + ConversionData& conv) { ConversionData::MeshCacheIndex idx(&item, mat_index); ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx); if (it != conv.cached_meshes.end()) { @@ -899,18 +855,18 @@ bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item, // ------------------------------------------------------------------------------------------------ void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item, - const std::set& mesh_indices, unsigned int mat_index, - ConversionData& conv) -{ + const std::set& mesh_indices, + unsigned int mat_index, + ConversionData& conv) { ConversionData::MeshCacheIndex idx(&item, mat_index); conv.cached_meshes[idx] = mesh_indices; } // ------------------------------------------------------------------------------------------------ -bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid, - std::set& mesh_indices, - ConversionData& conv) -{ +bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, + unsigned int matid, + std::set& mesh_indices, + ConversionData& conv) { // determine material unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true); @@ -919,8 +875,9 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un if(mesh_indices.size()) { PopulateMeshCache(item,mesh_indices,localmatid,conv); } + } else { + return false; } - else return false; } return true; } @@ -929,4 +886,4 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un } // ! IFC } // ! Assimp -#endif +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.cpp index 90b627df8..13ea2d429 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,14 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCLoad.cpp - * @brief Implementation of the Industry Foundation Classes loader. - */ +/// @file IFCLoad.cpp +/// @brief Implementation of the Industry Foundation Classes loader. #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER #include #include +#include #include #ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC @@ -67,12 +66,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { template <> const char *LogFunctions::Prefix() { - static auto prefix = "IFC: "; - return prefix; + return "IFC: "; } } // namespace Assimp @@ -91,7 +90,6 @@ using namespace Assimp::IFC; IfcUnitAssignment IfcClosedShell IfcDoor - */ namespace { @@ -105,7 +103,7 @@ void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &co } // namespace -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Industry Foundation Classes (IFC) Importer", "", "", @@ -118,14 +116,6 @@ static const aiImporterDesc desc = { "ifc ifczip step stp" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -IFCImporter::IFCImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -IFCImporter::~IFCImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -185,7 +175,7 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // get file size, etc. unz_file_info fileInfo; char filename[256]; - unzGetCurrentFileInfo(zip, &fileInfo, filename, sizeof(filename), 0, 0, 0, 0); + unzGetCurrentFileInfo(zip, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0); if (GetExtension(filename) != "ifc") { continue; } @@ -195,7 +185,7 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy size_t total = 0; int read = 0; do { - int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX; + unsigned bufferSize = fileInfo.uncompressed_size < INT16_MAX ? static_cast(fileInfo.uncompressed_size) : INT16_MAX; void *buffer = malloc(bufferSize); read = unzReadCurrentFile(zip, buffer, bufferSize); if (read > 0) { @@ -210,7 +200,7 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy ThrowException("Failed to decompress IFC ZIP file"); } unzCloseCurrentFile(zip); - stream.reset(new MemoryIOStream(buff, fileInfo.uncompressed_size, true)); + stream = std::make_shared(buff, fileInfo.uncompressed_size, true); if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) { ThrowException("Found no IFC file member in IFCZIP file (1)"); } @@ -227,10 +217,10 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy #endif } - std::unique_ptr db(STEP::ReadFileHeader(stream)); + std::unique_ptr db(STEP::ReadFileHeader(std::move(stream))); const STEP::HeaderInfo &head = static_cast(*db).GetHeader(); - if (!head.fileSchema.size() || head.fileSchema.substr(0, 3) != "IFC") { + if (!head.fileSchema.size() || head.fileSchema.substr(0, 4) != "IFC2") { ThrowException("Unrecognized file schema: " + head.fileSchema); } @@ -255,7 +245,12 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // tell the reader for which types we need to simulate STEPs reverse indices static const char *const inverse_indices_to_track[] = { - "ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem" + "ifcrelcontainedinspatialstructure", + "ifcrelaggregates", + "ifcrelvoidselement", + "ifcreldefinesbyproperties", + "ifcpropertyset", + "ifcstyleditem" }; // feed the IFC schema into the reader and pre-parse all lines @@ -265,6 +260,8 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy ThrowException("missing IfcProject entity"); } + + ConversionData conv(*db, proj->To(), pScene, settings); SetUnits(conv); SetCoordinateSpace(conv); @@ -357,6 +354,11 @@ void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &co // ------------------------------------------------------------------------------------------------ void SetUnits(ConversionData &conv) { + if (conv.proj.UnitsInContext == nullptr) { + IFCImporter::LogError("Skipping conversion data, nullptr."); + return; + } + // see if we can determine the coordinate space used to express. for (size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i) { ConvertUnit(*conv.proj.UnitsInContext->Units[i], conv); @@ -927,4 +929,4 @@ void MakeTreeRelative(ConversionData &conv) { } // namespace -#endif +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.h b/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.h index 7b2c3aae6..d518650b7 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.h +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -87,8 +87,8 @@ public: int cylindricalTessellation; }; - IFCImporter(); - ~IFCImporter() override; + IFCImporter() = default; + ~IFCImporter() override = default; // -------------------- bool CanRead(const std::string &pFile, diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCMaterial.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCMaterial.cpp index 6cd0e5910..fd4003a67 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCMaterial.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCMaterial.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCMaterial.cpp - * @brief Implementation of conversion routines to convert IFC materials to aiMaterial - */ +/// @file IFCMaterial.cpp +/// @brief Implementation of conversion routines to convert IFC materials to aiMaterial #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER @@ -174,7 +172,6 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat aiString name; name.Set(""); - // ConvertColorToString( color, name); // look if there's already a default material with this base color for( size_t a = 0; a < conv.materials.size(); ++a ) { diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCOpenings.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCOpenings.cpp index b8b9b8e05..1d37dd8ef 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCOpenings.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCOpenings.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -38,56 +38,58 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCOpenings.cpp - * @brief Implements a subset of Ifc CSG operations for pouring - * holes for windows and doors into walls. - */ - +/// @file IFCOpenings.cpp +/// @brief Implements a subset of Ifc CSG operations for pouring +/// holes for windows and doors into walls. #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER + #include "IFCUtil.h" #include "Common/PolyTools.h" #include "PostProcessing/ProcessHelper.h" +#include "contrib/poly2tri/poly2tri/poly2tri.h" +#include "contrib/clipper/clipper.hpp" -#ifdef ASSIMP_USE_HUNTER -# include -# include -#else -# include "../contrib/poly2tri/poly2tri/poly2tri.h" -# include "../contrib/clipper/clipper.hpp" -#endif - -#include -#include #include +#include +#include +#include namespace Assimp { - namespace IFC { +namespace IFC { - using ClipperLib::ulong64; - // XXX use full -+ range ... - const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var +using ClipperLib::ulong64; - //#define to_int64(p) (static_cast( std::max( 0., std::min( static_cast((p)), 1.) ) * max_ulong64 )) -#define to_int64(p) (static_cast(static_cast((p) ) * max_ulong64 )) -#define from_int64(p) (static_cast((p)) / max_ulong64) -#define one_vec (IfcVector2(static_cast(1.0),static_cast(1.0))) +// XXX use full -+ range ... +const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var +AI_FORCE_INLINE ulong64 to_int64(IfcFloat p) { + return (static_cast(static_cast((p) ) * max_ulong64 )); +} - // fallback method to generate wall openings - bool TryAddOpenings_Poly2Tri(const std::vector& openings, - TempMesh& curmesh); +AI_FORCE_INLINE IfcFloat from_int64(ulong64 p) { + return (static_cast((p)) / max_ulong64); +} +AI_FORCE_INLINE void fillRectangle(const IfcVector2& pmin, const IfcVector2& pmax, std::vector& out) { + out.emplace_back(pmin.x, pmin.y); + out.emplace_back(pmin.x, pmax.y); + out.emplace_back(pmax.x, pmax.y); + out.emplace_back(pmax.x, pmin.y); +} -typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; -typedef std::map XYSortedField; +const IfcVector2 one_vec(IfcVector2(static_cast(1.0),static_cast(1.0))); +// fallback method to generate wall openings +bool TryAddOpenings_Poly2Tri(const std::vector& openings, TempMesh& curmesh); + +using BoundingBox = std::pair< IfcVector2, IfcVector2 >; +using XYSortedField = std::map; // ------------------------------------------------------------------------------------------------ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, - const std::vector< BoundingBox >& bbs, - std::vector& out) -{ + const std::vector< BoundingBox >& bbs, + std::vector& out) { if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { return; } @@ -113,10 +115,7 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& if (!found) { // the rectangle [pmin,pend] is opaque, fill it - out.push_back(pmin); - out.emplace_back(pmin.x,pmax.y); - out.push_back(pmax); - out.emplace_back(pmax.x,pmin.y); + fillRectangle(pmin, pmax, out); return; } @@ -141,19 +140,12 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& } if (bb.second.y > ylast) { - found = true; const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); if (ys - ylast > 0.0f) { QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out); } - // the following are the window vertices - - /*wnd.push_back(IfcVector2(xs,ys)); - wnd.push_back(IfcVector2(xs,ye)); - wnd.push_back(IfcVector2(xe,ye)); - wnd.push_back(IfcVector2(xe,ys));*/ ylast = ye; } } @@ -175,23 +167,19 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& } } -typedef std::vector Contour; -typedef std::vector SkipList; // should probably use int for performance reasons +using Contour = std::vector; +using SkipList = std::vector; // should probably use int for performance reasons -struct ProjectedWindowContour -{ +struct ProjectedWindowContour { Contour contour; BoundingBox bb; SkipList skiplist; bool is_rectangular; + ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular) + : contour(contour), bb(bb) , is_rectangular(is_rectangular) {} - ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular) - : contour(contour) - , bb(bb) - , is_rectangular(is_rectangular) - {} - + ~ProjectedWindowContour() = default; bool IsInvalid() const { return contour.empty(); @@ -206,19 +194,17 @@ struct ProjectedWindowContour } }; -typedef std::vector< ProjectedWindowContour > ContourVector; +using ContourVector = std::vector; // ------------------------------------------------------------------------------------------------ -bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) -{ +static bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) { // count the '=' case as non-overlapping but as adjacent to each other return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x && ibb.first.y < bb.second.y && ibb.second.y > bb.first.y; } // ------------------------------------------------------------------------------------------------ -bool IsDuplicateVertex(const IfcVector2& vv, const std::vector& temp_contour) -{ +static bool IsDuplicateVertex(const IfcVector2& vv, const std::vector& temp_contour) { // sanity check for duplicate vertices for(const IfcVector2& cp : temp_contour) { if ((cp-vv).SquareLength() < 1e-5f) { @@ -229,14 +215,13 @@ bool IsDuplicateVertex(const IfcVector2& vv, const std::vector& temp } // ------------------------------------------------------------------------------------------------ -void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector& temp_contour, - bool filter_duplicates = false) -{ +void ExtractVerticesFromClipper(const ClipperLib::Path& poly, std::vector& temp_contour, + bool filter_duplicates = false) { temp_contour.clear(); for(const ClipperLib::IntPoint& point : poly) { IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); - vv = std::max(vv,IfcVector2()); - vv = std::min(vv,one_vec); + vv = std::max(vv, IfcVector2()); + vv = std::min(vv, one_vec); if (!filter_duplicates || !IsDuplicateVertex(vv, temp_contour)) { temp_contour.push_back(vv); @@ -245,13 +230,12 @@ void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector()(newbb_min, newbb_max); - for(const ClipperLib::IntPoint& point : poly) { - IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); + for (const ClipperLib::IntPoint& point : poly) { + IfcVector2 vv = IfcVector2(from_int64(point.X), from_int64(point.Y)); // sanity rounding vv = std::max(vv,IfcVector2()); @@ -264,12 +248,9 @@ BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly) } // ------------------------------------------------------------------------------------------------ -void InsertWindowContours(const ContourVector& contours, - const std::vector& /*openings*/, - TempMesh& curmesh) -{ +void InsertWindowContours(const ContourVector& contours, const std::vector& /*openings*/, TempMesh& curmesh) { // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now - for(size_t i = 0; i < contours.size();++i) { + for (size_t i = 0; i < contours.size(); ++i) { const BoundingBox& bb = contours[i].bb; const std::vector& contour = contours[i].contour; if(contour.empty()) { @@ -286,8 +267,7 @@ void InsertWindowContours(const ContourVector& contours, const std::set::const_iterator end = verts.end(); if (verts.find(bb.first)!=end && verts.find(bb.second)!=end && verts.find(IfcVector2(bb.first.x,bb.second.y))!=end - && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end - ) { + && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end ) { continue; } } @@ -312,8 +292,7 @@ void InsertWindowContours(const ContourVector& contours, if (std::fabs(v.x-bb.first.x)& a, - const std::vector& b, - ClipperLib::ExPolygons& out) -{ +void MergeWindowContours (const std::vector& a, const std::vector& b, + ClipperLib::Paths& out) { out.clear(); ClipperLib::Clipper clipper; - ClipperLib::Polygon clip; + ClipperLib::Path clip; for(const IfcVector2& pip : a) { clip.emplace_back(to_int64(pip.x), to_int64(pip.y)); @@ -406,7 +377,7 @@ void MergeWindowContours (const std::vector& a, std::reverse(clip.begin(), clip.end()); } - clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.AddPath(clip, ClipperLib::ptSubject, true); clip.clear(); for(const IfcVector2& pip : b) { @@ -417,21 +388,19 @@ void MergeWindowContours (const std::vector& a, std::reverse(clip.begin(), clip.end()); } - clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.AddPath(clip, ClipperLib::ptSubject, true); clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); } // ------------------------------------------------------------------------------------------------ // Subtract a from b void MakeDisjunctWindowContours (const std::vector& a, - const std::vector& b, - ClipperLib::ExPolygons& out) -{ + const std::vector& b, + ClipperLib::Paths& out) { out.clear(); ClipperLib::Clipper clipper; - ClipperLib::Polygon clip; - + ClipperLib::Path clip; for(const IfcVector2& pip : a) { clip.emplace_back(to_int64(pip.x), to_int64(pip.y)); } @@ -440,7 +409,7 @@ void MakeDisjunctWindowContours (const std::vector& a, std::reverse(clip.begin(), clip.end()); } - clipper.AddPolygon(clip, ClipperLib::ptClip); + clipper.AddPath(clip, ClipperLib::ptClip, true); clip.clear(); for(const IfcVector2& pip : b) { @@ -451,30 +420,28 @@ void MakeDisjunctWindowContours (const std::vector& a, std::reverse(clip.begin(), clip.end()); } - clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.AddPath(clip, ClipperLib::ptSubject, true); clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); } // ------------------------------------------------------------------------------------------------ -void CleanupWindowContour(ProjectedWindowContour& window) -{ +void CleanupWindowContour(ProjectedWindowContour& window) { std::vector scratch; std::vector& contour = window.contour; - ClipperLib::Polygon subject; + ClipperLib::Path subject; ClipperLib::Clipper clipper; - ClipperLib::ExPolygons clipped; + ClipperLib::Paths clipped; for(const IfcVector2& pip : contour) { subject.emplace_back(to_int64(pip.x), to_int64(pip.y)); } - clipper.AddPolygon(subject,ClipperLib::ptSubject); + clipper.AddPath(subject,ClipperLib::ptSubject, true); clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); // This should yield only one polygon or something went wrong if (clipped.size() != 1) { - // Empty polygon? drop the contour altogether if(clipped.empty()) { IFCImporter::LogError("error during polygon clipping, window contour is degenerate"); @@ -486,28 +453,25 @@ void CleanupWindowContour(ProjectedWindowContour& window) IFCImporter::LogError("error during polygon clipping, window contour is not convex"); } - ExtractVerticesFromClipper(clipped[0].outer, scratch); // Assume the bounding box doesn't change during this operation + ExtractVerticesFromClipper(clipped[0], scratch); } // ------------------------------------------------------------------------------------------------ -void CleanupWindowContours(ContourVector& contours) -{ +void CleanupWindowContours(ContourVector& contours) { // Use PolyClipper to clean up window contours try { for(ProjectedWindowContour& window : contours) { CleanupWindowContour(window); } - } - catch (const char* sx) { + } catch (const char* sx) { IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: " + std::string(sx) + ")"); } } // ------------------------------------------------------------------------------------------------ -void CleanupOuterContour(const std::vector& contour_flat, TempMesh& curmesh) -{ +void CleanupOuterContour(const std::vector& contour_flat, TempMesh& curmesh) { std::vector vold; std::vector iold; @@ -516,12 +480,11 @@ void CleanupOuterContour(const std::vector& contour_flat, TempMesh& // Fix the outer contour using polyclipper try { - - ClipperLib::Polygon subject; + ClipperLib::Path subject; ClipperLib::Clipper clipper; - ClipperLib::ExPolygons clipped; + ClipperLib::Paths clipped; - ClipperLib::Polygon clip; + ClipperLib::Path clip; clip.reserve(contour_flat.size()); for(const IfcVector2& pip : contour_flat) { clip.emplace_back(to_int64(pip.x), to_int64(pip.y)); @@ -550,18 +513,15 @@ void CleanupOuterContour(const std::vector& contour_flat, TempMesh& std::reverse(subject.begin(), subject.end()); } - clipper.AddPolygon(subject,ClipperLib::ptSubject); - clipper.AddPolygon(clip,ClipperLib::ptClip); + clipper.AddPath(subject,ClipperLib::ptSubject, true); + clipper.AddPath(clip,ClipperLib::ptClip, true); clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); - for(const ClipperLib::ExPolygon& ex : clipped) { - iold.push_back(static_cast(ex.outer.size())); - for(const ClipperLib::IntPoint& point : ex.outer) { - vold.emplace_back( - from_int64(point.X), - from_int64(point.Y), - 0.0f); + for(const ClipperLib::Path& ex : clipped) { + iold.push_back(static_cast(ex.size())); + for(const ClipperLib::IntPoint& point : ex) { + vold.emplace_back(from_int64(point.X), from_int64(point.Y), 0.0f); } } @@ -570,8 +530,7 @@ void CleanupOuterContour(const std::vector& contour_flat, TempMesh& clipper.Clear(); } } - } - catch (const char* sx) { + } catch (const char* sx) { IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: " + std::string(sx) + ")"); @@ -583,17 +542,14 @@ void CleanupOuterContour(const std::vector& contour_flat, TempMesh& std::swap(iold,curmesh.mVertcnt); } -typedef std::vector OpeningRefs; -typedef std::vector OpeningRefVector; - -typedef std::vector ; +using OpeningRefVector = std::vector; +using ContourRefVector = std::vector -> ContourRefVector; + Contour::const_iterator> >; // ------------------------------------------------------------------------------------------------ -bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) -{ +bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) { // TODO: I'm pretty sure there is a much more compact way to check this const IfcFloat epsilon = Math::getEpsilon(); return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || @@ -606,9 +562,8 @@ bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) // Check if m0,m1 intersects n0,n1 assuming same ordering of the points in the line segments // output the intersection points on n0,n1 bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, - const IfcVector2& m0, const IfcVector2& m1, - IfcVector2& out0, IfcVector2& out1) -{ + const IfcVector2& m0, const IfcVector2& m1, + IfcVector2& out0, IfcVector2& out1) { const IfcVector2 n0_to_n1 = n1 - n0; const IfcVector2 n0_to_m0 = m0 - n0; @@ -647,8 +602,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) { s1 = 0.; } - } - else { + } else { s0 = n0_to_m0.y / n0_to_n1.y; s1 = n0_to_m1.y / n0_to_n1.y; @@ -681,8 +635,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, } // ------------------------------------------------------------------------------------------------ -void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) -{ +void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) { const IfcFloat sqlen_epsilon = static_cast(Math::getEpsilon()); const BoundingBox& bb = (*current).bb; @@ -697,13 +650,6 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& continue; } - // this left here to make clear we also run on the current contour - // to check for overlapping contour segments (which can happen due - // to projection artifacts). - //if(it == current) { - // continue; - //} - const bool is_me = it == current; const BoundingBox& ibb = (*it).bb; @@ -739,8 +685,7 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& ncontour.insert(ncontour.begin() + n, isect0); skiplist.insert(skiplist.begin() + n, true); - } - else { + } else { skiplist[n] = true; } @@ -758,15 +703,13 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& } // ------------------------------------------------------------------------------------------------ -AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) -{ +AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) { const IfcFloat dot_point_epsilon = static_cast(Math::getEpsilon()); return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon; } // ------------------------------------------------------------------------------------------------ -void FindBorderContours(ContourVector::iterator current) -{ +void FindBorderContours(ContourVector::iterator current) { const IfcFloat border_epsilon_upper = static_cast(1-1e-4); const IfcFloat border_epsilon_lower = static_cast(1e-4); @@ -786,20 +729,17 @@ void FindBorderContours(ContourVector::iterator current) // not have any geometry to close them (think of door openings). if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper || proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) { - if (outer_border) { ai_assert(cit != cbegin); if (LikelyBorder(proj_point - last_proj_point)) { skiplist[std::distance(cbegin, cit) - 1] = true; } - } - else if (cit == cbegin) { + } else if (cit == cbegin) { start_on_outer_border = true; } outer_border = true; - } - else { + } else { outer_border = false; } @@ -816,16 +756,14 @@ void FindBorderContours(ContourVector::iterator current) } // ------------------------------------------------------------------------------------------------ -AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta) -{ +AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta) { vdelta.x = std::fabs(vdelta.x); vdelta.y = std::fabs(vdelta.y); return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y)); } // ------------------------------------------------------------------------------------------------ -void FindLikelyCrossingLines(ContourVector::iterator current) -{ +void FindLikelyCrossingLines(ContourVector::iterator current) { SkipList& skiplist = (*current).skiplist; IfcVector2 last_proj_point; @@ -851,10 +789,9 @@ void FindLikelyCrossingLines(ContourVector::iterator current) // ------------------------------------------------------------------------------------------------ size_t CloseWindows(ContourVector& contours, - const IfcMatrix4& minv, - OpeningRefVector& contours_to_openings, - TempMesh& curmesh) -{ + const IfcMatrix4& minv, + OpeningRefVector& contours_to_openings, + TempMesh& curmesh) { size_t closed = 0; // For all contour points, check if one of the assigned openings does // already have points assigned to it. In this case, assume this is @@ -963,8 +900,7 @@ size_t CloseWindows(ContourVector& contours, if (drop_this_edge) { curmesh.mVerts.pop_back(); curmesh.mVerts.pop_back(); - } - else { + } else { curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv); curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point); @@ -991,16 +927,13 @@ size_t CloseWindows(ContourVector& contours, curmesh.mVertcnt.pop_back(); curmesh.mVerts.pop_back(); curmesh.mVerts.pop_back(); - } - else { + } else { curmesh.mVerts.push_back(reverseCountourFaces ? start0 : start1); curmesh.mVerts.push_back(reverseCountourFaces ? start1 : start0); } } } - } - else { - + } else { const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); for(TempOpening* opening : refs) { ai_assert(opening->wallPoints.empty()); @@ -1017,8 +950,7 @@ size_t CloseWindows(ContourVector& contours, } // ------------------------------------------------------------------------------------------------ -void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) -{ +void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) { ai_assert(curmesh.IsEmpty()); std::vector quads; @@ -1044,8 +976,7 @@ void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) } // ------------------------------------------------------------------------------------------------ -void Quadrify(const ContourVector& contours, TempMesh& curmesh) -{ +void Quadrify(const ContourVector& contours, TempMesh& curmesh) { std::vector bbs; bbs.reserve(contours.size()); @@ -1057,12 +988,17 @@ void Quadrify(const ContourVector& contours, TempMesh& curmesh) } // ------------------------------------------------------------------------------------------------ -IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh& in_mesh, - bool &ok, IfcVector3& nor_out) -{ +IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, + const TempMesh& in_mesh, + bool &ok, + IfcVector3& nor_out) { const std::vector& in_verts = in_mesh.mVerts; + if (in_verts.empty()){ + ok = false; + return IfcMatrix4(); + } + ok = true; - IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); if(!ok) { return IfcMatrix4(); @@ -1075,7 +1011,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh IfcFloat zcoord = 0; out_contour.reserve(in_verts.size()); - IfcVector3 vmin, vmax; MinMaxChooser()(vmin, vmax); @@ -1086,11 +1021,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh // (which are present, of course), this should be the same value for // all polygon vertices (assuming the polygon is planar). - // XXX this should be guarded, but we somehow need to pick a suitable - // epsilon - // if(coord != -1.0f) { - // assert(std::fabs(coord - vv.z) < 1e-3f); - // } zcoord += vv.z; vmin = std::min(vv, vmin); vmax = std::max(vv, vmax); @@ -1142,11 +1072,10 @@ IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh // ------------------------------------------------------------------------------------------------ bool GenerateOpenings(std::vector& openings, - TempMesh& curmesh, - bool check_intersection, - bool generate_connection_geometry, - const IfcVector3& wall_extrusion_axis) -{ + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis) { OpeningRefVector contours_to_openings; // Try to derive a solid base plane within the current surface for use as @@ -1182,8 +1111,7 @@ bool GenerateOpenings(std::vector& openings, IfcVector3 norm_extrusion_dir = opening.extrusionDir; if (norm_extrusion_dir.SquareLength() > 1e-10) { norm_extrusion_dir.Normalize(); - } - else { + } else { norm_extrusion_dir = IfcVector3(); } @@ -1248,10 +1176,8 @@ bool GenerateOpenings(std::vector& openings, const IfcVector3 v = m * x; IfcVector2 vv(v.x, v.y); - //if(check_intersection) { - dmin = std::min(dmin, v.z); - dmax = std::max(dmax, v.z); - //} + dmin = std::min(dmin, v.z); + dmax = std::max(dmax, v.z); // sanity rounding vv = std::max(vv,IfcVector2()); @@ -1260,8 +1186,7 @@ bool GenerateOpenings(std::vector& openings, if(side_flag) { vpmin = std::min(vpmin,vv); vpmax = std::max(vpmax,vv); - } - else { + } else { vpmin2 = std::min(vpmin2,vv); vpmax2 = std::max(vpmax2,vv); } @@ -1309,28 +1234,25 @@ bool GenerateOpenings(std::vector& openings, // See if this BB intersects or is in close adjacency to any other BB we have so far. for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) { const BoundingBox& ibb = (*it).bb; - if (BoundingBoxesOverlapping(ibb, bb)) { - if (!(*it).is_rectangular) { is_rectangle = false; } const std::vector& other = (*it).contour; - ClipperLib::ExPolygons poly; + ClipperLib::Paths poly; // First check whether subtracting the old contour (to which ibb belongs) // from the new contour (to which bb belongs) yields an updated bb which // no longer overlaps ibb MakeDisjunctWindowContours(other, temp_contour, poly); if(poly.size() == 1) { - - const BoundingBox newbb = GetBoundingBox(poly[0].outer); + const BoundingBox newbb = GetBoundingBox(poly[0]); if (!BoundingBoxesOverlapping(ibb, newbb )) { // Good guy bounding box bb = newbb ; - ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + ExtractVerticesFromClipper(poly[0], temp_contour, false); continue; } } @@ -1342,15 +1264,13 @@ bool GenerateOpenings(std::vector& openings, if (poly.size() > 1) { return TryAddOpenings_Poly2Tri(openings, curmesh); - } - else if (poly.size() == 0) { + } else if (poly.empty()) { IFCImporter::LogWarn("ignoring duplicate opening"); temp_contour.clear(); break; - } - else { + } else { IFCImporter::LogVerboseDebug("merging overlapping openings"); - ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + ExtractVerticesFromClipper(poly[0], temp_contour, false); // Generate the union of the bounding boxes bb.first = std::min(bb.first, ibb.first); @@ -1428,9 +1348,14 @@ bool GenerateOpenings(std::vector& openings, return true; } -std::vector GetContourInPlane2D(const std::shared_ptr& mesh,IfcMatrix3 planeSpace, - IfcVector3 planeNor,IfcFloat planeOffset, - IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) { +std::vector GetContourInPlane2D(const std::shared_ptr& mesh, + IfcMatrix3 planeSpace, + IfcVector3 planeNor, + IfcFloat planeOffset, + IfcVector3 extrusionDir, + IfcVector3& wall_extrusion, + bool& first, + bool& ok) { std::vector contour; const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize(); @@ -1447,7 +1372,7 @@ std::vector GetContourInPlane2D(const std::shared_ptr& mes const std::vector& va = mesh->mVerts; if(va.size() <= 2) { std::stringstream msg; - msg << "Skipping: Only " << va.size() << " verticies in opening mesh."; + msg << "Skipping: Only " << va.size() << " vertices in opening mesh."; IFCImporter::LogDebug(msg.str().c_str()); ok = false; return contour; @@ -1493,7 +1418,6 @@ static void logSegment(std::pair segment) { std::vector> GetContoursInPlane3D(const std::shared_ptr& mesh,IfcMatrix3 planeSpace, IfcFloat planeOffset) { - { std::stringstream msg; msg << "GetContoursInPlane3D: planeSpace is \n"; @@ -1520,8 +1444,8 @@ std::vector> GetContoursInPlane3D(const std::shared_ptr< IFCImporter::LogInfo(msg.str().c_str()); } - if(nVertices <= 2) // not a plane, a point or line - { + // not a plane, a point or line + if(nVertices <= 2) { std::stringstream msg; msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)"; IFCImporter::LogWarn(msg.str().c_str()); @@ -1551,15 +1475,12 @@ std::vector> GetContoursInPlane3D(const std::shared_ptr< if(std::fabs(vn.z - planeOffset) < close) { // on the plane intersection = vn; - } - else if((vn.z > planeOffset) != (vp.z > planeOffset)) - { + } else if((vn.z > planeOffset) != (vp.z > planeOffset)) { // passes through the plane auto vdir = vn - vp; auto scale = (planeOffset - vp.z) / vdir.z; intersection = vp + scale * vdir; - } - else { + } else { // nowhere near - move on continue; } @@ -1574,15 +1495,13 @@ std::vector> GetContoursInPlane3D(const std::shared_ptr< logSegment(s); lineSegments.push_back(s); // next firstpoint should be this one - } - else { + } else { // store the first intersection point firstPoint.x = intersection.x; firstPoint.y = intersection.y; gotFirstPoint = true; } - } - else { + } else { // now got the second point, so store the pair IfcVector2 secondPoint(intersection.x,intersection.y); auto s = std::pair(firstPoint,secondPoint); @@ -1690,29 +1609,28 @@ std::vector> GetContoursInPlane3D(const std::shared_ptr< return contours; } -std::vector> GetContoursInPlane(const std::shared_ptr& mesh,IfcMatrix3 planeSpace, - IfcVector3 planeNor,IfcFloat planeOffset, - IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) { - - if(mesh->mVertcnt.size() == 1) - { +std::vector> GetContoursInPlane(const std::shared_ptr& mesh, + IfcMatrix3 planeSpace, + IfcVector3 planeNor, + IfcFloat planeOffset, + IfcVector3 extrusionDir, + IfcVector3& wall_extrusion, + bool& first) { + if(mesh->mVertcnt.size() == 1) { bool ok; auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok); if(ok) - return std::vector> {contour}; + return std::vector> {std::move(contour)}; else return std::vector> {}; - } - else - { + } else { return GetContoursInPlane3D(mesh,planeSpace,planeOffset); } } // ------------------------------------------------------------------------------------------------ bool TryAddOpenings_Poly2Tri(const std::vector& openings, - TempMesh& curmesh) -{ + TempMesh& curmesh) { IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); std::vector& out = curmesh.mVerts; @@ -1745,14 +1663,6 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, // keep Z offset in the plane coordinate system. Ignoring precision issues // (which are present, of course), this should be the same value for // all polygon vertices (assuming the polygon is planar). - - - // XXX this should be guarded, but we somehow need to pick a suitable - // epsilon - // if(coord != -1.0f) { - // assert(std::fabs(coord - vv.z) < 1e-3f); - // } - coord = vv.z; vmin = std::min(IfcVector2(vv.x, vv.y), vmin); @@ -1770,15 +1680,13 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, // If this happens then the projection must have been wrong. ai_assert(vmax.Length()); - ClipperLib::ExPolygons clipped; - ClipperLib::Polygons holes_union; - + ClipperLib::Paths clipped; + ClipperLib::Paths holes_union; IfcVector3 wall_extrusion; bool first = true; try { - ClipperLib::Clipper clipper_holes; for(const TempOpening& t : openings) { @@ -1786,7 +1694,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, for(auto& contour : contours) { // scale to clipping space - ClipperLib::Polygon hole; + ClipperLib::Path hole; for(IfcVector2& pip : contour) { pip.x = (pip.x - vmin.x) / vmax.x; pip.y = (pip.y - vmin.y) / vmax.y; @@ -1796,16 +1704,9 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, if(!ClipperLib::Orientation(hole)) { std::reverse(hole.begin(),hole.end()); - // assert(ClipperLib::Orientation(hole)); } - /*ClipperLib::Polygons pol_temp(1), pol_temp2(1); - pol_temp[0] = hole; - - ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0); - hole = pol_temp2[0];*/ - - clipper_holes.AddPolygon(hole,ClipperLib::ptSubject); + clipper_holes.AddPath(hole,ClipperLib::ptSubject, true); { std::stringstream msg; msg << "- added polygon "; @@ -1828,7 +1729,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, // Now that we have the big union of all holes, subtract it from the outer contour // to obtain the final polygon to feed into the triangulator. { - ClipperLib::Polygon poly; + ClipperLib::Path poly; for(IfcVector2& pip : contour_flat) { pip.x = (pip.x - vmin.x) / vmax.x; pip.y = (pip.y - vmin.y) / vmax.y; @@ -1840,16 +1741,14 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, std::reverse(poly.begin(), poly.end()); } clipper_holes.Clear(); - clipper_holes.AddPolygon(poly,ClipperLib::ptSubject); + clipper_holes.AddPath(poly,ClipperLib::ptSubject, true); - clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip); + clipper_holes.AddPaths(holes_union,ClipperLib::ptClip, true); clipper_holes.Execute(ClipperLib::ctDifference,clipped, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } - - } - catch (const char* sx) { + } catch (const char* sx) { IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: " + std::string(sx) + ")"); @@ -1863,13 +1762,13 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, old_vertcnt.swap(curmesh.mVertcnt); std::vector< std::vector > contours; - for(ClipperLib::ExPolygon& clip : clipped) { + for(ClipperLib::Path &clip : clipped) { contours.clear(); // Build the outer polygon contour line for feeding into poly2tri std::vector contour_points; - for(ClipperLib::IntPoint& point : clip.outer) { + for(ClipperLib::IntPoint& point : clip) { contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); } @@ -1880,16 +1779,14 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, // happen in production use if the input data is broken. An assertion would be // inappropriate. cdt = new p2t::CDT(contour_points); - } - catch(const std::exception& e) { + } catch(const std::exception& e) { IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + std::string(e.what()) + ")"); continue; } - // Build the poly2tri inner contours for all holes we got from ClipperLib - for(ClipperLib::Polygon& opening : clip.holes) { + for(ClipperLib::Path& opening : holes_union) { contours.emplace_back(); std::vector& contour = contours.back(); @@ -1904,8 +1801,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, try { // Note: See above cdt->Triangulate(); - } - catch(const std::exception& e) { + } catch(const std::exception& e) { IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + std::string(e.what()) + ")"); continue; @@ -1944,12 +1840,11 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings, return result; } - - } // ! IFC +} // ! IFC } // ! Assimp #undef to_int64 #undef from_int64 #undef one_vec -#endif +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCProfile.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCProfile.cpp index cee36c022..72a96c29b 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCProfile.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCProfile.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCProfile.cpp - * @brief Read profile and curves entities from IFC files - */ +/// @file IFCProfile.cpp +/// @brief Read profile and curves entities from IFC files #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER @@ -52,8 +50,9 @@ namespace Assimp { namespace IFC { // ------------------------------------------------------------------------------------------------ -void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/) -{ +void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, + TempMesh& meshout, + ConversionData& /*conv*/) { // this won't produce a valid mesh, it just spits out a list of vertices IfcVector3 t; for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) { @@ -64,8 +63,9 @@ void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, Conv } // ------------------------------------------------------------------------------------------------ -bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv) -{ +bool ProcessCurve(const Schema_2x3::IfcCurve& curve, + TempMesh& meshout, + ConversionData& conv) { std::unique_ptr cv(Curve::Convert(curve,conv)); if (!cv) { IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName()); @@ -90,20 +90,23 @@ bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, Convers } // ------------------------------------------------------------------------------------------------ -void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv) -{ +void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, + TempMesh& meshout, + ConversionData& conv) { ProcessCurve(def.OuterCurve,meshout,conv); } // ------------------------------------------------------------------------------------------------ -void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv) -{ +void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, + TempMesh& meshout, + ConversionData& conv) { ProcessCurve(def.Curve,meshout,conv); } // ------------------------------------------------------------------------------------------------ -void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv) -{ +void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, + TempMesh& meshout, + ConversionData& conv) { if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr()) { const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f; @@ -113,8 +116,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de meshout.mVerts.emplace_back(-x,-y, 0.f ); meshout.mVerts.emplace_back( x,-y, 0.f ); meshout.mVertcnt.push_back(4); - } - else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr()) { + } else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr()) { if(def.ToPtr()) { // TODO } @@ -129,8 +131,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de } meshout.mVertcnt.push_back(static_cast(segments)); - } - else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr()) { + } else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr()) { // construct simplified IBeam shape const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2; const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2; @@ -150,8 +151,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de meshout.mVerts.emplace_back(ishape->OverallWidth,0,0); meshout.mVertcnt.push_back(12); - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName()); return; } @@ -162,18 +162,14 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de } // ------------------------------------------------------------------------------------------------ -bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) -{ +bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) { if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr()) { ProcessClosedProfile(*cprofile,meshout,conv); - } - else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr()) { + } else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr()) { ProcessOpenProfile(*copen,meshout,conv); - } - else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr()) { + } else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr()) { ProcessParametrizedProfile(*cparam,meshout,conv); - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName()); return false; } diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp index a6f7ae3eb..c625f1daf 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp @@ -1065,28 +1065,28 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcRoo if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->GlobalId, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRoot to be a `IfcGloballyUniqueId`")); } - } while(0); + } while (false); do { // convert the 'OwnerHistory' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->OwnerHistory, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRoot to be a `IfcOwnerHistory`")); } - } while(0); + } while (false); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRoot to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRoot to be a `IfcText`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcObjectDefinition* in) @@ -1152,28 +1152,28 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->ContextOfItems, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentation to be a `IfcRepresentationContext`")); } - } while(0); + } while (false); do { // convert the 'RepresentationIdentifier' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentation to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'RepresentationType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RepresentationType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRepresentation to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'Items' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } try { GenericConvert( in->Items, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRepresentation to be a `SET [1:?] OF IfcRepresentationItem`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcShapeModel* in) @@ -1239,8 +1239,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcO if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ObjectType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcObject to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcControl* in) @@ -1292,21 +1292,21 @@ template <> size_t GenericFill(const DB& db, const LIS if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProductRepresentation to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProductRepresentation to be a `IfcText`")); } - } while(0); + } while (false); do { // convert the 'Representations' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->Representations, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcProductRepresentation to be a `LIST [1:?] OF IfcRepresentation`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcProduct* in) @@ -1318,15 +1318,15 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ObjectPlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProduct to be a `IfcObjectPlacement`")); } - } while(0); + } while (false); do { // convert the 'Representation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Representation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProduct to be a `IfcProductRepresentation`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcElement* in) @@ -1338,8 +1338,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Tag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcElement to be a `IfcIdentifier`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcDistributionElement* in) @@ -1376,14 +1376,14 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Segments, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurve to be a `LIST [1:?] OF IfcCompositeCurveSegment`")); } - } while(0); + } while (false); do { // convert the 'SelfIntersect' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurve to be a `LOGICAL`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, Ifc2DCompositeCurve* in) @@ -1402,28 +1402,28 @@ template <> size_t GenericFill(const DB& db, if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Axis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } - } while(0); + } while (false); do { // convert the 'Axis2' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Axis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } - } while(0); + } while (false); do { // convert the 'LocalOrigin' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->LocalOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCartesianTransformationOperator to be a `IfcCartesianPoint`")); } - } while(0); + } while (false); do { // convert the 'Scale' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Scale, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCartesianTransformationOperator to be a `REAL`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCartesianTransformationOperator3D* in) @@ -1435,8 +1435,8 @@ template <> size_t GenericFill(const DB& d if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Axis3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCartesianTransformationOperator3D to be a `IfcDirection`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcProperty* in) @@ -1447,15 +1447,15 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProperty to be a `IfcIdentifier`")); } - } while(0); + } while (false); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProperty to be a `IfcText`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSimpleProperty* in) @@ -1499,8 +1499,8 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcElementarySurface to be a `IfcAxis2Placement3D`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPlane* in) @@ -1517,20 +1517,20 @@ template <> size_t GenericFill(const DB& db, const LIST& param if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Operator, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBooleanResult to be a `IfcBooleanOperator`")); } - } while(0); + } while (false); do { // convert the 'FirstOperand' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->FirstOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBooleanResult to be a `IfcBooleanOperand`")); } - } while(0); + } while (false); do { // convert the 'SecondOperand' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->SecondOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBooleanResult to be a `IfcBooleanOperand`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBooleanClippingResult* in) @@ -1553,8 +1553,8 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Outer, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcManifoldSolidBrep to be a `IfcClosedShell`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFlowTerminalType* in) @@ -1632,13 +1632,13 @@ template <> size_t GenericFill(const DB& db, const LIST& par std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelFillsElement to be a `IfcOpeningElement`")); } - } while(0); + } while (false); do { // convert the 'RelatedBuildingElement' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelFillsElement to be a `IfcElement`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcProcedure* in) @@ -1683,13 +1683,13 @@ template <> size_t GenericFill(const DB& db, std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatedElements, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelContainedInSpatialStructure to be a `SET [1:?] OF IfcProduct`")); } - } while(0); + } while (false); do { // convert the 'RelatingStructure' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatingStructure, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelContainedInSpatialStructure to be a `IfcSpatialStructureElement`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcTopologicalRepresentationItem* in) @@ -1774,8 +1774,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I std::shared_ptr arg = params[base++]; try { GenericConvert( in->DirectionRatios, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcDirection to be a `LIST [2:3] OF REAL`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcProfileDef* in) @@ -1786,15 +1786,15 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->ProfileType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProfileDef to be a `IfcProfileTypeEnum`")); } - } while(0); + } while (false); do { // convert the 'ProfileName' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ProfileName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProfileDef to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcParameterizedProfileDef* in) @@ -1805,8 +1805,8 @@ template <> size_t GenericFill(const DB& db, const L if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcParameterizedProfileDef to be a `IfcAxis2Placement2D`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCShapeProfileDef* in) @@ -1912,8 +1912,8 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCircleProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCircleHollowProfileDef* in) @@ -1923,8 +1923,8 @@ template <> size_t GenericFill(const DB& db, const LI std::shared_ptr arg = params[base++]; try { GenericConvert( in->WallThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCircleHollowProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPlacement* in) @@ -1935,8 +1935,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Location, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPlacement to be a `IfcCartesianPoint`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcAxis2Placement3D* in) @@ -1947,14 +1947,14 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement3D to be a `IfcDirection`")); } - } while(0); + } while (false); do { // convert the 'RefDirection' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcAxis2Placement3D to be a `IfcDirection`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPresentationStyle* in) @@ -1966,8 +1966,8 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyle to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcEquipmentElement* in) @@ -1984,18 +1984,18 @@ template <> size_t GenericFill(const DB& db, const LIS std::shared_ptr arg = params[base++]; try { GenericConvert( in->Transition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurveSegment to be a `IfcTransitionCode`")); } - } while(0); + } while (false); do { // convert the 'SameSense' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->SameSense, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurveSegment to be a `BOOLEAN`")); } - } while(0); + } while (false); do { // convert the 'ParentCurve' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->ParentCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCompositeCurveSegment to be a `IfcCurve`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcRectangleProfileDef* in) @@ -2006,14 +2006,14 @@ template <> size_t GenericFill(const DB& db, const LIST& if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'YDim' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBuildingElementProxy* in) @@ -2108,13 +2108,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) break; try { GenericConvert( in->PlacementRelTo, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLocalPlacement to be a `IfcObjectPlacement`")); } - } while(0); + } while (false); do { // convert the 'RelativePlacement' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelativePlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLocalPlacement to be a `IfcAxis2Placement`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSweptAreaSolid* in) @@ -2125,14 +2125,14 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->SweptArea, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptAreaSolid to be a `IfcProfileDef`")); } - } while(0); + } while (false); do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptAreaSolid to be a `IfcAxis2Placement3D`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcRevolvedAreaSolid* in) @@ -2142,13 +2142,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p std::shared_ptr arg = params[base++]; try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRevolvedAreaSolid to be a `IfcAxis1Placement`")); } - } while(0); + } while (false); do { // convert the 'Angle' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Angle, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRevolvedAreaSolid to be a `IfcPlaneAngleMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStructuralSurfaceConnection* in) @@ -2172,29 +2172,29 @@ template <> size_t GenericFill(const DB& db, const LIST& para std::shared_ptr arg = params[base++]; try { GenericConvert( in->Directrix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptDiskSolid to be a `IfcCurve`")); } - } while(0); + } while (false); do { // convert the 'Radius' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'InnerRadius' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->InnerRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'StartParam' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->StartParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } - } while(0); + } while (false); do { // convert the 'EndParam' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->EndParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcHalfSpaceSolid* in) @@ -2205,14 +2205,14 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->BaseSurface, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcHalfSpaceSolid to be a `IfcSurface`")); } - } while(0); + } while (false); do { // convert the 'AgreementFlag' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->AgreementFlag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcHalfSpaceSolid to be a `BOOLEAN`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPolygonalBoundedHalfSpace* in) @@ -2222,13 +2222,13 @@ template <> size_t GenericFill(const DB& db, const std::shared_ptr arg = params[base++]; try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } - } while(0); + } while (false); do { // convert the 'PolygonalBoundary' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcTimeSeriesSchedule* in) @@ -2253,24 +2253,24 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc if (dynamic_cast(&*arg)) break; try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProject to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'Phase' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Phase, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProject to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'RepresentationContexts' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->RepresentationContexts, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcProject to be a `SET [1:?] OF IfcRepresentationContext`")); } - } while(0); + } while (false); do { // convert the 'UnitsInContext' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->UnitsInContext, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcProject to be a `IfcUnitAssignment`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcEvaporatorType* in) @@ -2329,28 +2329,28 @@ template <> size_t GenericFill(const DB& db, const LIST& params std::shared_ptr arg = params[base++]; try { GenericConvert( in->BasisCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcTrimmedCurve to be a `IfcCurve`")); } - } while(0); + } while (false); do { // convert the 'Trim1' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Trim1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } - } while(0); + } while (false); do { // convert the 'Trim2' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Trim2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } - } while(0); + } while (false); do { // convert the 'SenseAgreement' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->SenseAgreement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcTrimmedCurve to be a `BOOLEAN`")); } - } while(0); + } while (false); do { // convert the 'MasterRepresentation' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->MasterRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcTrimmedCurve to be a `IfcTrimmingPreference`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcRelDefines* in) @@ -2361,8 +2361,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDefines to be a `SET [1:?] OF IfcObject`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcRelDefinesByProperties* in) @@ -2373,8 +2373,8 @@ template <> size_t GenericFill(const DB& db, const LI if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDefinesByProperties to be a `IfcPropertySetDefinition`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcActor* in) @@ -2406,8 +2406,8 @@ template <> size_t GenericFill(const DB& db, const L if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Curve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryOpenProfileDef to be a `IfcBoundedCurve`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPermit* in) @@ -2572,14 +2572,14 @@ template <> size_t GenericFill(const DB& db, const LIST& param if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->RelatingObject, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDecomposes to be a `IfcObjectDefinition`")); } - } while(0); + } while (false); do { // convert the 'RelatedObjects' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDecomposes to be a `SET [1:?] OF IfcObjectDefinition`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCovering* in) @@ -2596,8 +2596,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If std::shared_ptr arg = params[base++]; try { GenericConvert( in->Points, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyline to be a `LIST [2:?] OF IfcCartesianPoint`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPath* in) @@ -2628,13 +2628,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, std::shared_ptr arg = params[base++]; try { GenericConvert( in->MappingSource, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMappedItem to be a `IfcRepresentationMap`")); } - } while(0); + } while (false); do { // convert the 'MappingTarget' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->MappingTarget, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMappedItem to be a `IfcCartesianTransformationOperator`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcRectangularPyramid* in) @@ -2660,14 +2660,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Dimensions, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcNamedUnit to be a `IfcDimensionalExponents`")); } - } while(0); + } while (false); do { // convert the 'UnitType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->UnitType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcNamedUnit to be a `IfcUnitEnum`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcContextDependentUnit* in) @@ -2721,14 +2721,18 @@ template <> size_t GenericFill(const DB& db, const L if (dynamic_cast(&*arg)) break; try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSpatialStructureElement to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'CompositionType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } + if (dynamic_cast(&*arg)) { + // Consider assigning the default value as in->CompositionType = "ELEMENT". + break; + } try { GenericConvert( in->CompositionType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSpatialStructureElement to be a `IfcElementCompositionEnum`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBuilding* in) @@ -2739,20 +2743,20 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcBuilding to be a `IfcLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'ElevationOfTerrain' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcBuilding to be a `IfcLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'BuildingAddress' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->BuildingAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcBuilding to be a `IfcPostalAddress`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcConnectedFaceSet* in) @@ -2763,8 +2767,8 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->CfsFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConnectedFaceSet to be a `SET [1:?] OF IfcFace`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcOpenShell* in) @@ -2789,8 +2793,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcCo if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConic to be a `IfcAxis2Placement`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCoveringType* in) @@ -2836,33 +2840,33 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'OverallDepth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->OverallDepth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'WebThickness' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->WebThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'FlangeThickness' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } try { GenericConvert( in->FlangeThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'FilletRadius' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->FilletRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcAsymmetricIShapeProfileDef* in) @@ -2935,14 +2939,14 @@ template <> size_t GenericFill(const DB& db, const LIST& p std::shared_ptr arg = params[base++]; try { GenericConvert( in->ListValues, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertyListValue to be a `LIST [1:?] OF IfcValue`")); } - } while(0); + } while (false); do { // convert the 'Unit' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertyListValue to be a `IfcUnit`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFurnitureStandard* in) @@ -2967,14 +2971,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcDoo if (dynamic_cast(&*arg)) break; try { GenericConvert( in->OverallHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'OverallWidth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStyledItem* in) @@ -2986,21 +2990,21 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Item, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcStyledItem to be a `IfcRepresentationItem`")); } - } while(0); + } while (false); do { // convert the 'Styles' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcStyledItem to be a `SET [1:?] OF IfcPresentationStyleAssignment`")); } - } while(0); + } while (false); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcStyledItem to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcAnnotationOccurrence* in) @@ -3025,8 +3029,8 @@ template <> size_t GenericFill(const DB& db, const if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->OuterCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryClosedProfileDef to be a `IfcCurve`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcArbitraryProfileDefWithVoids* in) @@ -3043,13 +3047,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcLin std::shared_ptr arg = params[base++]; try { GenericConvert( in->Pnt, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLine to be a `IfcCartesianPoint`")); } - } while(0); + } while (false); do { // convert the 'Dir' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Dir, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLine to be a `IfcVector`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFlowSegmentType* in) @@ -3074,14 +3078,14 @@ template <> size_t GenericFill(const DB& db, const LIST& if (dynamic_cast(&*arg)) break; try { GenericConvert( in->NominalValue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertySingleValue to be a `IfcValue`")); } - } while(0); + } while (false); do { // convert the 'Unit' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertySingleValue to be a `IfcUnit`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcAlarmType* in) @@ -3113,8 +3117,8 @@ template <> size_t GenericFill(const DB& db, const LIST& if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->SurfaceColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleShading to be a `IfcColourRgb`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcPumpType* in) diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp index 0d7051195..24acd658d 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp @@ -61,13 +61,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params std::shared_ptr arg = params[base++]; try { GenericConvert( in->Side, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyle to be a `IfcSurfaceSide`")); } - } while(0); + } while (false); do { // convert the 'Styles' argument std::shared_ptr arg = params[ base++ ]; try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyle to be a `SET [1:5] OF IfcSurfaceStyleElementSelect`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcAnnotationSurface* in) @@ -120,8 +120,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcFac if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Bounds, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFace to be a `SET [1:?] OF IfcFaceBound`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStructuralSurfaceMember* in) @@ -175,8 +175,8 @@ template <> size_t GenericFill(const DB& db, const LIST& if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcColourSpecification to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcVector* in) @@ -186,13 +186,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcV std::shared_ptr arg = params[base++]; try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcVector to be a `IfcDirection`")); } - } while(0); + } while (false); do { // convert the 'Magnitude' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Magnitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcVector to be a `IfcLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBeam* in) @@ -209,18 +209,18 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I std::shared_ptr arg = params[base++]; try { GenericConvert( in->Red, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } - } while(0); + } while (false); do { // convert the 'Green' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Green, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } - } while(0); + } while (false); do { // convert the 'Blue' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Blue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStructuralPlanarAction* in) @@ -245,32 +245,32 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSit if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RefLatitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } - } while(0); + } while (false); do { // convert the 'RefLongitude' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RefLongitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } - } while(0); + } while (false); do { // convert the 'RefElevation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RefElevation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcSite to be a `IfcLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'LandTitleNumber' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->LandTitleNumber, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcSite to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'SiteAddress' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->SiteAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 13 to IfcSite to be a `IfcPostalAddress`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcDiscreteAccessoryType* in) @@ -414,32 +414,32 @@ template <> size_t GenericFill(const DB& db, const LIST& params if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Degree, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBSplineCurve to be a `INTEGER`")); } - } while(0); + } while (false); do { // convert the 'ControlPointsList' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->ControlPointsList, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBSplineCurve to be a `LIST [2:?] OF IfcCartesianPoint`")); } - } while(0); + } while (false); do { // convert the 'CurveForm' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->CurveForm, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBSplineCurve to be a `IfcBSplineCurveForm`")); } - } while(0); + } while (false); do { // convert the 'ClosedCurve' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } try { GenericConvert( in->ClosedCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBSplineCurve to be a `LOGICAL`")); } - } while(0); + } while (false); do { // convert the 'SelfIntersect' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcBSplineCurve to be a `LOGICAL`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBezierCurve* in) @@ -476,8 +476,8 @@ template <> size_t GenericFill(const DB& db, const LI std::shared_ptr arg = params[base++]; try { GenericConvert( in->SbsmBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcShellBasedSurfaceModel to be a `SET [1:?] OF IfcShell`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcActionRequest* in) @@ -494,13 +494,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p std::shared_ptr arg = params[base++]; try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcExtrudedAreaSolid to be a `IfcDirection`")); } - } while(0); + } while (false); do { // convert the 'Depth' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Depth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcExtrudedAreaSolid to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSystem* in) @@ -524,13 +524,13 @@ template <> size_t GenericFill(const DB& db, const LIST& par std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } - } while(0); + } while (false); do { // convert the 'RelatedOpeningElement' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSurfaceCurveSweptAreaSolid* in) @@ -548,14 +548,14 @@ template <> size_t GenericFill(c if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Scale2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } - } while(0); + } while (false); do { // convert the 'Scale3' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Scale3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCurtainWallType* in) @@ -636,8 +636,8 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (dynamic_cast(&*arg)) break; try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement2D to be a `IfcDirection`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSpaceProgram* in) @@ -660,8 +660,8 @@ template <> size_t GenericFill(const DB& db, const LIST& para std::shared_ptr arg = params[base++]; try { GenericConvert( in->Coordinates, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianPoint to be a `LIST [1:3] OF IfcLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBoundedSurface* in) @@ -684,8 +684,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If std::shared_ptr arg = params[base++]; try { GenericConvert( in->Polygon, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyLoop to be a `LIST [3:?] OF IfcCartesianPoint`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcTerminatorSymbol* in) @@ -718,15 +718,15 @@ template <> size_t GenericFill(const DB& db, const LIS if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ContextIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationContext to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'ContextType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ContextType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationContext to be a `IfcLabel`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcGeometricRepresentationContext* in) @@ -737,28 +737,28 @@ template <> size_t GenericFill(const DB& db, if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcGeometricRepresentationContext to be a `IfcDimensionCount`")); } - } while(0); + } while (false); do { // convert the 'Precision' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Precision, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcGeometricRepresentationContext to be a `REAL`")); } - } while(0); + } while (false); do { // convert the 'WorldCoordinateSystem' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcGeometricRepresentationContext to be a `IfcAxis2Placement`")); } - } while(0); + } while (false); do { // convert the 'TrueNorth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; try { GenericConvert( in->TrueNorth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcGeometricRepresentationContext to be a `IfcDirection`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCurveBoundedPlane* in) @@ -776,13 +776,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcS if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Prefix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSIUnit to be a `IfcSIPrefix`")); } - } while(0); + } while (false); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSIUnit to be a `IfcSIUnitName`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStructuralReaction* in) @@ -807,8 +807,8 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis1Placement to be a `IfcDirection`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcElectricApplianceType* in) @@ -860,13 +860,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p std::shared_ptr arg = params[base++]; try { GenericConvert( in->MappingOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationMap to be a `IfcAxis2Placement`")); } - } while(0); + } while (false); do { // convert the 'MappedRepresentation' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->MappedRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationMap to be a `IfcRepresentation`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcClosedShell* in) @@ -1014,13 +1014,13 @@ template <> size_t GenericFill(const DB& db, const LIST& par std::shared_ptr arg = params[base++]; try { GenericConvert( in->ValueComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMeasureWithUnit to be a `IfcValue`")); } - } while(0); + } while (false); do { // convert the 'UnitComponent' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->UnitComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMeasureWithUnit to be a `IfcUnit`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSlabType* in) @@ -1127,8 +1127,8 @@ template <> size_t GenericFill(const DB& db, const LIS std::shared_ptr arg = params[base++]; try { GenericConvert( in->FbsmFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBasedSurfaceModel to be a `SET [1:?] OF IfcConnectedFaceSet`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcEnergyConversionDevice* in) @@ -1174,14 +1174,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } try { GenericConvert( in->Bound, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBound to be a `IfcLoop`")); } - } while(0); + } while (false); do { // convert the 'Orientation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcFaceBound to be a `BOOLEAN`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFaceOuterBound* in) @@ -1218,13 +1218,13 @@ template <> size_t GenericFill(const DB& db, const LIST& par std::shared_ptr arg = params[base++]; try { GenericConvert( in->UsageName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcComplexProperty to be a `IfcIdentifier`")); } - } while(0); + } while (false); do { // convert the 'HasProperties' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcComplexProperty to be a `SET [1:?] OF IfcProperty`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFooting* in) @@ -1276,8 +1276,8 @@ template <> size_t GenericFill(const DB& db, const LIST& para std::shared_ptr arg = params[base++]; try { GenericConvert( in->Units, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcUnitAssignment to be a `SET [1:?] OF IfcUnit`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFlowTerminal* in) @@ -1309,13 +1309,13 @@ template <> size_t GenericFill(const DB& db, const LIST& par if (dynamic_cast(&*arg)) break; try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcElementQuantity to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'Quantities' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->Quantities, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcElementQuantity to be a `SET [1:?] OF IfcPhysicalQuantity`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcCurtainWall* in) @@ -1381,8 +1381,8 @@ template <> size_t GenericFill(const DB& db, con std::shared_ptr arg = params[base++]; try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyleAssignment to be a `SET [1:?] OF IfcPresentationStyleSelect`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcStructuralCurveMember* in) @@ -1420,14 +1420,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSp std::shared_ptr arg = params[base++]; try { GenericConvert( in->InteriorOrExteriorSpace, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSpace to be a `IfcInternalOrExternalEnum`")); } - } while(0); + } while (false); do { // convert the 'ElevationWithFlooring' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSpace to be a `IfcLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcHeatExchangerType* in) @@ -1486,8 +1486,8 @@ template <> size_t GenericFill(const DB& db, const std::shared_ptr arg = params[base++]; try { GenericConvert( in->Textures, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleWithTextures to be a `LIST [1:?] OF IfcSurfaceTexture`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcBoundingBox* in) @@ -1497,23 +1497,23 @@ template <> size_t GenericFill(const DB& db, const LIST& params, std::shared_ptr arg = params[base++]; try { GenericConvert( in->Corner, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBoundingBox to be a `IfcCartesianPoint`")); } - } while(0); + } while (false); do { // convert the 'XDim' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'YDim' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'ZDim' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->ZDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcWallType* in) @@ -1537,8 +1537,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcC std::shared_ptr arg = params[base++]; try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCircle to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcOffsetCurve2D* in) @@ -1625,13 +1625,13 @@ template <> size_t GenericFill(const DB& db, const LIST& std::shared_ptr arg = params[base++]; try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcConversionBasedUnit to be a `IfcLabel`")); } - } while(0); + } while (false); do { // convert the 'ConversionFactor' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->ConversionFactor, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcConversionBasedUnit to be a `IfcMeasureWithUnit`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcGeometricRepresentationSubContext* in) @@ -1746,13 +1746,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc std::shared_ptr arg = params[base++]; try { GenericConvert( in->SemiAxis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } - } while(0); + } while (false); do { // convert the 'SemiAxis2' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->SemiAxis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcProductDefinitionShape* in) @@ -1818,8 +1818,8 @@ template <> size_t GenericFill(const DB& db, const LIST& params, std::shared_ptr arg = params[base++]; try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcPropertySet to be a `SET [1:?] OF IfcProperty`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcSurfaceStyleRendering* in) @@ -1830,49 +1830,49 @@ template <> size_t GenericFill(const DB& db, const LIS if (dynamic_cast(&*arg)) break; try { GenericConvert( in->Transparency, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyleRendering to be a `IfcNormalisedRatioMeasure`")); } - } while(0); + } while (false); do { // convert the 'DiffuseColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->DiffuseColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } - } while(0); + } while (false); do { // convert the 'TransmissionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->TransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } - } while(0); + } while (false); do { // convert the 'DiffuseTransmissionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } - } while(0); + } while (false); do { // convert the 'ReflectionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->ReflectionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } - } while(0); + } while (false); do { // convert the 'SpecularColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->SpecularColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } - } while(0); + } while (false); do { // convert the 'SpecularHighlight' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; try { GenericConvert( in->SpecularHighlight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSurfaceStyleRendering to be a `IfcSpecularHighlightSelect`")); } - } while(0); + } while (false); do { // convert the 'ReflectanceMethod' argument std::shared_ptr arg = params[base++]; try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSurfaceStyleRendering to be a `IfcReflectanceMethodEnum`")); } - } while(0); - return base; + } while (false); + return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcDistributionPort* in) diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.cpp b/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.cpp index 6cf104833..3977e22b5 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.cpp +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,14 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFCUtil.cpp - * @brief Implementation of conversion routines for some common Ifc helper entities. - */ +/// @file IFCUtil.cpp +/// @brief Implementation of conversion routines for some common Ifc helper entities. #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER #include "AssetLib/IFC/IFCUtil.h" #include "Common/PolyTools.h" +#include "Geometry/GeometryUtils.h" #include "PostProcessing/ProcessHelper.h" namespace Assimp { @@ -65,8 +64,7 @@ void TempOpening::Transform(const IfcMatrix4& mat) { } // ------------------------------------------------------------------------------------------------ -aiMesh* TempMesh::ToMesh() -{ +aiMesh* TempMesh::ToMesh() { ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0))); if (mVerts.empty()) { @@ -104,36 +102,31 @@ aiMesh* TempMesh::ToMesh() } // ------------------------------------------------------------------------------------------------ -void TempMesh::Clear() -{ +void TempMesh::Clear() { mVerts.clear(); mVertcnt.clear(); } // ------------------------------------------------------------------------------------------------ -void TempMesh::Transform(const IfcMatrix4& mat) -{ +void TempMesh::Transform(const IfcMatrix4& mat) { for(IfcVector3& v : mVerts) { v *= mat; } } // ------------------------------------------------------------------------------ -IfcVector3 TempMesh::Center() const -{ - return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast(mVerts.size())); +IfcVector3 TempMesh::Center() const { + return mVerts.empty() ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast(mVerts.size())); } // ------------------------------------------------------------------------------------------------ -void TempMesh::Append(const TempMesh& other) -{ +void TempMesh::Append(const TempMesh& other) { mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end()); mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end()); } // ------------------------------------------------------------------------------------------------ -void TempMesh::RemoveDegenerates() -{ +void TempMesh::RemoveDegenerates() { // The strategy is simple: walk the mesh and compute normals using // Newell's algorithm. The length of the normals gives the area // of the polygons, which is close to zero for lines. @@ -166,11 +159,9 @@ void TempMesh::RemoveDegenerates() } // ------------------------------------------------------------------------------------------------ -IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) -{ +IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) { std::vector temp((cnt+2)*3); - for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs ) - { + for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs ) { const IfcVector3& v = vtcs[vofs]; temp[i++] = v.x; temp[i++] = v.y; @@ -184,9 +175,8 @@ IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bo // ------------------------------------------------------------------------------------------------ void TempMesh::ComputePolygonNormals(std::vector& normals, - bool normalize, - size_t ofs) const -{ + bool normalize, + size_t ofs) const { size_t max_vcount = 0; std::vector::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit; for(iit = begin; iit != end; ++iit) { @@ -235,7 +225,7 @@ IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const { struct CompareVector { bool operator () (const IfcVector3& a, const IfcVector3& b) const { IfcVector3 d = a - b; - IfcFloat eps = ai_epsilon; + constexpr IfcFloat eps = ai_epsilon; return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps); } }; @@ -249,29 +239,27 @@ struct FindVector { }; // ------------------------------------------------------------------------------------------------ -void TempMesh::FixupFaceOrientation() -{ +void TempMesh::FixupFaceOrientation() { const IfcVector3 vavg = Center(); // create a list of start indices for all faces to allow random access to faces std::vector faceStartIndices(mVertcnt.size()); - for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a ) + for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a ) { faceStartIndices[a] = i; + } // list all faces on a vertex std::map, CompareVector> facesByVertex; - for( size_t a = 0; a < mVertcnt.size(); ++a ) - { - for( size_t b = 0; b < mVertcnt[a]; ++b ) + for( size_t a = 0; a < mVertcnt.size(); ++a ) { + for( size_t b = 0; b < mVertcnt[a]; ++b ) { facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a); + } } // determine neighbourhood for all polys std::vector neighbour(mVerts.size(), SIZE_MAX); std::vector tempIntersect(10); - for( size_t a = 0; a < mVertcnt.size(); ++a ) - { - for( size_t b = 0; b < mVertcnt[a]; ++b ) - { + for( size_t a = 0; a < mVertcnt.size(); ++a ) { + for( size_t b = 0; b < mVertcnt[a]; ++b ) { size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a]; const std::vector& facesOnB = facesByVertex[mVerts[ib]]; const std::vector& facesOnNB = facesByVertex[mVerts[nib]]; @@ -280,10 +268,12 @@ void TempMesh::FixupFaceOrientation() std::vector::iterator sectend = std::set_intersection( facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart); - if( std::distance(sectstart, sectend) != 2 ) + if( std::distance(sectstart, sectend) != 2 ) { continue; - if( *sectstart == a ) + } + if( *sectstart == a ) { ++sectstart; + } neighbour[ib] = *sectstart; } } @@ -292,15 +282,14 @@ void TempMesh::FixupFaceOrientation() // facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring // faces to have the same winding until all faces have been tested. std::vector faceDone(mVertcnt.size(), false); - while( std::count(faceDone.begin(), faceDone.end(), false) != 0 ) - { + while( std::count(faceDone.begin(), faceDone.end(), false) != 0 ) { // find the farthest of the remaining faces size_t farthestIndex = SIZE_MAX; IfcFloat farthestDistance = -1.0; - for( size_t a = 0; a < mVertcnt.size(); ++a ) - { - if( faceDone[a] ) + for( size_t a = 0; a < mVertcnt.size(); ++a ) { + if( faceDone[a] ) { continue; + } IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a], mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]); IfcFloat dst = (faceCenter - vavg).SquareLength(); @@ -314,8 +303,7 @@ void TempMesh::FixupFaceOrientation() / IfcFloat(mVertcnt[farthestIndex]); // We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in // the file. - if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) - { + if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) { size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex]; std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc); std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc); @@ -332,19 +320,18 @@ void TempMesh::FixupFaceOrientation() todo.push_back(farthestIndex); // go over its neighbour faces recursively and adapt their winding order to match the farthest face - while( !todo.empty() ) - { + while( !todo.empty() ) { size_t tdf = todo.back(); size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf]; todo.pop_back(); // check its neighbours - for( size_t a = 0; a < vc; ++a ) - { + for( size_t a = 0; a < vc; ++a ) { // ignore neighbours if we already checked them size_t nbi = neighbour[vsi + a]; - if( nbi == SIZE_MAX || faceDone[nbi] ) + if( nbi == SIZE_MAX || faceDone[nbi] ) { continue; + } const IfcVector3& vp = mVerts[vsi + a]; size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi]; @@ -387,32 +374,8 @@ void TempMesh::RemoveAdjacentDuplicates() { IfcVector3 vmin,vmax; ArrayBounds(&*base, cnt ,vmin,vmax); - const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast(1e9); - //const IfcFloat dotepsilon = 1e-9; - - //// look for vertices that lie directly on the line between their predecessor and their - //// successor and replace them with either of them. - - //for(size_t i = 0; i < cnt; ++i) { - // IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt); - // const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1); - // const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength(); - // if (!l0 || !l1) { - // continue; - // } - - // const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::sqrt(l1)); - - // if ( d >= 1.f-dotepsilon ) { - // v1 = v0; - // } - // else if ( d < -1.f+dotepsilon ) { - // v2 = v1; - // continue; - // } - //} - + // drop any identical, adjacent vertices. this pass will collect the dropouts // of the previous pass as a side-effect. FuzzyVectorCompare fz(epsilon); @@ -439,78 +402,58 @@ void TempMesh::RemoveAdjacentDuplicates() { } // ------------------------------------------------------------------------------------------------ -void TempMesh::Swap(TempMesh& other) -{ +void TempMesh::Swap(TempMesh& other) { mVertcnt.swap(other.mVertcnt); mVerts.swap(other.mVerts); } // ------------------------------------------------------------------------------------------------ -bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in) -{ +bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in) { return (std::string)in == "TRUE" || (std::string)in == "T"; } // ------------------------------------------------------------------------------------------------ -IfcFloat ConvertSIPrefix(const std::string& prefix) -{ +IfcFloat ConvertSIPrefix(const std::string& prefix) { if (prefix == "EXA") { return 1e18f; - } - else if (prefix == "PETA") { + } else if (prefix == "PETA") { return 1e15f; - } - else if (prefix == "TERA") { + } else if (prefix == "TERA") { return 1e12f; - } - else if (prefix == "GIGA") { + } else if (prefix == "GIGA") { return 1e9f; - } - else if (prefix == "MEGA") { + } else if (prefix == "MEGA") { return 1e6f; - } - else if (prefix == "KILO") { + } else if (prefix == "KILO") { return 1e3f; - } - else if (prefix == "HECTO") { + } else if (prefix == "HECTO") { return 1e2f; - } - else if (prefix == "DECA") { + } else if (prefix == "DECA") { return 1e-0f; - } - else if (prefix == "DECI") { + } else if (prefix == "DECI") { return 1e-1f; - } - else if (prefix == "CENTI") { + } else if (prefix == "CENTI") { return 1e-2f; - } - else if (prefix == "MILLI") { + } else if (prefix == "MILLI") { return 1e-3f; - } - else if (prefix == "MICRO") { + } else if (prefix == "MICRO") { return 1e-6f; - } - else if (prefix == "NANO") { + } else if (prefix == "NANO") { return 1e-9f; - } - else if (prefix == "PICO") { + } else if (prefix == "PICO") { return 1e-12f; - } - else if (prefix == "FEMTO") { + } else if (prefix == "FEMTO") { return 1e-15f; - } - else if (prefix == "ATTO") { + } else if (prefix == "ATTO") { return 1e-18f; - } - else { + } else { IFCImporter::LogError("Unrecognized SI prefix: ", prefix); return 1; } } // ------------------------------------------------------------------------------------------------ -void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) -{ +void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) { out.r = static_cast( in.Red ); out.g = static_cast( in.Green ); out.b = static_cast( in.Blue ); @@ -518,8 +461,10 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) } // ------------------------------------------------------------------------------------------------ -void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base) -{ +void ConvertColor(aiColor4D& out, + const Schema_2x3::IfcColourOrFactor& in, + ConversionData& conv, + const aiColor4D* base) { if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { out.r = out.g = out.b = static_cast(*r); if(base) { @@ -527,20 +472,18 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,Conver out.g *= static_cast( base->g ); out.b *= static_cast( base->b ); out.a = static_cast( base->a ); + } else { + out.a = 1.0; } - else out.a = 1.0; - } - else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr(conv.db)) { + } else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr(conv.db)) { ConvertColor(out,*rgb); - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity"); } } // ------------------------------------------------------------------------------------------------ -void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in) -{ +void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in) { out = IfcVector3(); for(size_t i = 0; i < in.Coordinates.size(); ++i) { out[static_cast(i)] = in.Coordinates[i]; @@ -548,15 +491,13 @@ void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& } // ------------------------------------------------------------------------------------------------ -void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in) -{ +void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in) { ConvertDirection(out,in.Orientation); out *= in.Magnitude; } // ------------------------------------------------------------------------------------------------ -void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) -{ +void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) { out = IfcVector3(); for(size_t i = 0; i < in.DirectionRatios.size(); ++i) { out[static_cast(i)] = in.DirectionRatios[i]; @@ -570,8 +511,7 @@ void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) } // ------------------------------------------------------------------------------------------------ -void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z) -{ +void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z) { out.a1 = x.x; out.b1 = x.y; out.c1 = x.z; @@ -586,8 +526,7 @@ void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, } // ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in) -{ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in) { IfcVector3 loc; ConvertCartesianPoint(loc,in.Location); @@ -611,8 +550,7 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D } // ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in) -{ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in) { IfcVector3 loc; ConvertCartesianPoint(loc,in.Location); @@ -628,34 +566,28 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D } // ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in) -{ +void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in) { ConvertCartesianPoint(pos,in.Location); if (in.Axis) { ConvertDirection(axis,in.Axis.Get()); - } - else { + } else { axis = IfcVector3(0.f,0.f,1.f); } } // ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv) -{ +void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv) { if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr(conv.db)) { ConvertAxisPlacement(out,*pl3); - } - else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr(conv.db)) { + } else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr(conv.db)) { ConvertAxisPlacement(out,*pl2); - } - else { + } else { IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity"); } } // ------------------------------------------------------------------------------------------------ -void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op) -{ +void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op) { IfcVector3 loc; ConvertCartesianPoint(loc,op.LocalOrigin); @@ -676,14 +608,12 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra IfcMatrix4::Translation(loc,locm); AssignMatrixAxes(out,x,y,z); - IfcVector3 vscale; if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr()) { vscale.x = nuni->Scale?op.Scale.Get():1.f; vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f; vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f; - } - else { + } else { const IfcFloat sc = op.Scale?op.Scale.Get():1.f; vscale = IfcVector3(sc,sc,sc); } @@ -694,8 +624,7 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra out = locm * out * s; } - } // ! IFC } // ! Assimp -#endif +#endif // ASSIMP_BUILD_NO_IFC_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.h b/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.h index f9063ce22..885bcb48f 100644 --- a/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.h +++ b/Engine/lib/assimp/code/AssetLib/IFC/IFCUtil.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/IQM/IQMImporter.cpp b/Engine/lib/assimp/code/AssetLib/IQM/IQMImporter.cpp index 2bea66c6b..432c12113 100644 --- a/Engine/lib/assimp/code/AssetLib/IQM/IQMImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/IQM/IQMImporter.cpp @@ -59,7 +59,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // http://sauerbraten.org/iqm/ // https://github.com/lsalzman/iqm - inline void swap_block( uint32_t *block, size_t size ){ (void)block; // suppress 'unreferenced formal parameter' MSVC warning size >>= 2; @@ -67,7 +66,7 @@ inline void swap_block( uint32_t *block, size_t size ){ AI_SWAP4( block[ i ] ); } -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Inter-Quake Model Importer", "", "", @@ -100,13 +99,6 @@ bool IQMImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool c if (!pIOHandler) { return true; } - /* - * don't use CheckMagicToken because that checks with swapped bytes too, leading to false - * positives. This magic is not uint32_t, but char[4], so memcmp is the best way - - const char* tokens[] = {"3DMO", "3dmo"}; - return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4); - */ std::unique_ptr pStream(pIOHandler->Open(pFile, "rb")); unsigned char data[15]; if (!pStream || 15 != pStream->Read(data, 1, 15)) { @@ -127,7 +119,7 @@ const aiImporterDesc *IQMImporter::GetInfo() const { void IQMImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { // Read file into memory std::unique_ptr pStream(pIOHandler->Open(file, "rb")); - if (!pStream.get()) { + if (!pStream) { throw DeadlyImportError("Failed to open file ", file, "."); } diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.cpp b/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.cpp index c4058a4c2..12ce86260 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of the Irr importer class */ +#include "assimp/Exceptional.h" +#include "assimp/StringComparison.h" #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER #include "AssetLib/Irr/IRRLoader.h" @@ -62,28 +64,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include - using namespace Assimp; -static const aiImporterDesc desc = { - "Irrlicht Scene Reader", - "", - "", - "http://irrlicht.sourceforge.net/", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "irr xml" +static constexpr aiImporterDesc desc = { + "Irrlicht Scene Reader", + "", + "", + "http://irrlicht.sourceforge.net/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "irr xml" }; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer IRRImporter::IRRImporter() : - fps(), configSpeedFlag() { - // empty + fps(), configSpeedFlag() { + // empty } // ------------------------------------------------------------------------------------------------ @@ -93,154 +93,154 @@ IRRImporter::~IRRImporter() = default; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { - static const char *tokens[] = { "irr_scene" }; - return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); + static const char *tokens[] = { "irr_scene" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); } // ------------------------------------------------------------------------------------------------ const aiImporterDesc *IRRImporter::GetInfo() const { - return &desc; + return &desc; } // ------------------------------------------------------------------------------------------------ void IRRImporter::SetupProperties(const Importer *pImp) { - // read the output frame rate of all node animation channels - fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100); - if (fps < 10.) { - ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration"); - fps = 100; - } + // read the output frame rate of all node animation channels + fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100); + if (fps < 10.) { + ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration"); + fps = 100; + } - // AI_CONFIG_FAVOUR_SPEED - configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0)); + // AI_CONFIG_FAVOUR_SPEED + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0)); } // ------------------------------------------------------------------------------------------------ // Build a mesh that consists of a single squad (a side of a skybox) aiMesh *IRRImporter::BuildSingleQuadMesh(const SkyboxVertex &v1, - const SkyboxVertex &v2, - const SkyboxVertex &v3, - const SkyboxVertex &v4) { - // allocate and prepare the mesh - aiMesh *out = new aiMesh(); + const SkyboxVertex &v2, + const SkyboxVertex &v3, + const SkyboxVertex &v4) { + // allocate and prepare the mesh + aiMesh *out = new aiMesh(); - out->mPrimitiveTypes = aiPrimitiveType_POLYGON; - out->mNumFaces = 1; + out->mPrimitiveTypes = aiPrimitiveType_POLYGON; + out->mNumFaces = 1; - // build the face - out->mFaces = new aiFace[1]; - aiFace &face = out->mFaces[0]; + // build the face + out->mFaces = new aiFace[1]; + aiFace &face = out->mFaces[0]; - face.mNumIndices = 4; - face.mIndices = new unsigned int[4]; - for (unsigned int i = 0; i < 4; ++i) - face.mIndices[i] = i; + face.mNumIndices = 4; + face.mIndices = new unsigned int[4]; + for (unsigned int i = 0; i < 4; ++i) + face.mIndices[i] = i; - out->mNumVertices = 4; + out->mNumVertices = 4; - // copy vertex positions - aiVector3D *vec = out->mVertices = new aiVector3D[4]; - *vec++ = v1.position; - *vec++ = v2.position; - *vec++ = v3.position; - *vec = v4.position; + // copy vertex positions + aiVector3D *vec = out->mVertices = new aiVector3D[4]; + *vec++ = v1.position; + *vec++ = v2.position; + *vec++ = v3.position; + *vec = v4.position; - // copy vertex normals - vec = out->mNormals = new aiVector3D[4]; - *vec++ = v1.normal; - *vec++ = v2.normal; - *vec++ = v3.normal; - *vec = v4.normal; + // copy vertex normals + vec = out->mNormals = new aiVector3D[4]; + *vec++ = v1.normal; + *vec++ = v2.normal; + *vec++ = v3.normal; + *vec = v4.normal; - // copy texture coordinates - vec = out->mTextureCoords[0] = new aiVector3D[4]; - *vec++ = v1.uv; - *vec++ = v2.uv; - *vec++ = v3.uv; - *vec = v4.uv; - return out; + // copy texture coordinates + vec = out->mTextureCoords[0] = new aiVector3D[4]; + *vec++ = v1.uv; + *vec++ = v2.uv; + *vec++ = v3.uv; + *vec = v4.uv; + return out; } // ------------------------------------------------------------------------------------------------ void IRRImporter::BuildSkybox(std::vector &meshes, std::vector materials) { - // Update the material of the skybox - replace the name and disable shading for skyboxes. - for (unsigned int i = 0; i < 6; ++i) { - aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i))); + // Update the material of the skybox - replace the name and disable shading for skyboxes. + for (unsigned int i = 0; i < 6; ++i) { + aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i))); - aiString s; - s.length = ::ai_snprintf(s.data, MAXLEN, "SkyboxSide_%u", i); - out->AddProperty(&s, AI_MATKEY_NAME); + aiString s; + s.length = ::ai_snprintf(s.data, AI_MAXLEN, "SkyboxSide_%u", i); + out->AddProperty(&s, AI_MATKEY_NAME); - int shading = aiShadingMode_NoShading; - out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL); - } + int shading = aiShadingMode_NoShading; + out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL); + } - // Skyboxes are much more difficult. They are represented - // by six single planes with different textures, so we'll - // need to build six meshes. + // Skyboxes are much more difficult. They are represented + // by six single planes with different textures, so we'll + // need to build six meshes. - const ai_real l = 10.0; // the size used by Irrlicht + const ai_real l = 10.0; // the size used by Irrlicht - // FRONT SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0), - SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0), - SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0), - SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 6u); + // FRONT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0), + SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0), + SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0), + SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 6u); - // LEFT SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0), - SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0), - SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0), - SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 5u); + // LEFT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0), + SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0), + SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0), + SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 5u); - // BACK SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0), - SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0), - SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0), - SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 4u); + // BACK SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0), + SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0), + SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0), + SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 4u); - // RIGHT SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0), - SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0), - SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0), - SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 3u); + // RIGHT SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0), + SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0), + SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0), + SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 3u); - // TOP SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0), - SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0), - SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0), - SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 2u); + // TOP SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0), + SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0), + SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0), + SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 2u); - // BOTTOM SIDE - meshes.push_back(BuildSingleQuadMesh( - SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0), - SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0), - SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0), - SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0))); - meshes.back()->mMaterialIndex = static_cast(materials.size() - 1u); + // BOTTOM SIDE + meshes.push_back(BuildSingleQuadMesh( + SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0), + SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0), + SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0), + SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0))); + meshes.back()->mMaterialIndex = static_cast(materials.size() - 1u); } // ------------------------------------------------------------------------------------------------ void IRRImporter::CopyMaterial(std::vector &materials, - std::vector> &inmaterials, - unsigned int &defMatIdx, - aiMesh *mesh) { - if (inmaterials.empty()) { - // Do we have a default material? If not we need to create one - if (UINT_MAX == defMatIdx) { - defMatIdx = (unsigned int)materials.size(); - //TODO: add this materials to someone? - /*aiMaterial* mat = new aiMaterial(); + std::vector> &inmaterials, + unsigned int &defMatIdx, + aiMesh *mesh) { + if (inmaterials.empty()) { + // Do we have a default material? If not we need to create one + if (UINT_MAX == defMatIdx) { + defMatIdx = (unsigned int)materials.size(); + // TODO: add this materials to someone? + /*aiMaterial* mat = new aiMaterial(); aiString s; s.Set(AI_DEFAULT_MATERIAL_NAME); @@ -248,1110 +248,1104 @@ void IRRImporter::CopyMaterial(std::vector &materials, aiColor3D c(0.6f,0.6f,0.6f); mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);*/ - } - mesh->mMaterialIndex = defMatIdx; - return; - } else if (inmaterials.size() > 1) { - ASSIMP_LOG_INFO("IRR: Skipping additional materials"); - } + } + mesh->mMaterialIndex = defMatIdx; + return; + } else if (inmaterials.size() > 1) { + ASSIMP_LOG_INFO("IRR: Skipping additional materials"); + } - mesh->mMaterialIndex = (unsigned int)materials.size(); - materials.push_back(inmaterials[0].first); + mesh->mMaterialIndex = (unsigned int)materials.size(); + materials.push_back(inmaterials[0].first); } // ------------------------------------------------------------------------------------------------ inline int ClampSpline(int idx, int size) { - return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx)); + return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx)); } // ------------------------------------------------------------------------------------------------ inline void FindSuitableMultiple(int &angle) { - if (angle < 3) - angle = 3; - else if (angle < 10) - angle = 10; - else if (angle < 20) - angle = 20; - else if (angle < 30) - angle = 30; + if (angle < 3) + angle = 3; + else if (angle < 10) + angle = 10; + else if (angle < 20) + angle = 20; + else if (angle < 30) + angle = 30; } // ------------------------------------------------------------------------------------------------ void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vector &anims) { - ai_assert(nullptr != root && nullptr != real); + ai_assert(nullptr != root && nullptr != real); - // XXX totally WIP - doesn't produce proper results, need to evaluate - // whether there's any use for Irrlicht's proprietary scene format - // outside Irrlicht ... - // This also applies to the above function of FindSuitableMultiple and ClampSpline which are - // solely used in this function + // XXX totally WIP - doesn't produce proper results, need to evaluate + // whether there's any use for Irrlicht's proprietary scene format + // outside Irrlicht ... + // This also applies to the above function of FindSuitableMultiple and ClampSpline which are + // solely used in this function - if (root->animators.empty()) { - return; - } - unsigned int total(0); - for (std::list::iterator it = root->animators.begin(); it != root->animators.end(); ++it) { - if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) { - ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator"); - continue; - } - ++total; - } - if (!total) { - return; - } else if (1 == total) { - ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators"); - } + if (root->animators.empty()) { + return; + } + unsigned int total(0); + for (std::list::iterator it = root->animators.begin(); it != root->animators.end(); ++it) { + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) { + ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator"); + continue; + } + ++total; + } + if (!total) { + return; + } else if (1 == total) { + ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators"); + } - // NOTE: 1 tick == i millisecond + // NOTE: 1 tick == i millisecond - unsigned int cur = 0; - for (std::list::iterator it = root->animators.begin(); - it != root->animators.end(); ++it) { - if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue; + unsigned int cur = 0; + for (std::list::iterator it = root->animators.begin(); + it != root->animators.end(); ++it) { + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue; - Animator &in = *it; - aiNodeAnim *anim = new aiNodeAnim(); + Animator &in = *it; + aiNodeAnim *anim = new aiNodeAnim(); - if (cur != total - 1) { - // Build a new name - a prefix instead of a suffix because it is - // easier to check against - anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN, - "$INST_DUMMY_%i_%s", total - 1, - (root->name.length() ? root->name.c_str() : "")); + if (cur != total - 1) { + // Build a new name - a prefix instead of a suffix because it is + // easier to check against + anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, AI_MAXLEN, + "$INST_DUMMY_%i_%s", total - 1, + (root->name.length() ? root->name.c_str() : "")); - // we'll also need to insert a dummy in the node hierarchy. - aiNode *dummy = new aiNode(); + // we'll also need to insert a dummy in the node hierarchy. + aiNode *dummy = new aiNode(); - for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i) - if (real->mParent->mChildren[i] == real) - real->mParent->mChildren[i] = dummy; + for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i) + if (real->mParent->mChildren[i] == real) + real->mParent->mChildren[i] = dummy; - dummy->mParent = real->mParent; - dummy->mName = anim->mNodeName; + dummy->mParent = real->mParent; + dummy->mName = anim->mNodeName; - dummy->mNumChildren = 1; - dummy->mChildren = new aiNode *[dummy->mNumChildren]; - dummy->mChildren[0] = real; + dummy->mNumChildren = 1; + dummy->mChildren = new aiNode *[dummy->mNumChildren]; + dummy->mChildren[0] = real; - // the transformation matrix of the dummy node is the identity + // the transformation matrix of the dummy node is the identity - real->mParent = dummy; - } else - anim->mNodeName.Set(root->name); - ++cur; + real->mParent = dummy; + } else + anim->mNodeName.Set(root->name); + ++cur; - switch (in.type) { - case Animator::ROTATION: { - // ----------------------------------------------------- - // find out how long a full rotation will take - // This is the least common multiple of 360.f and all - // three euler angles. Although we'll surely find a - // possible multiple (haha) it could be somewhat large - // for our purposes. So we need to modify the angles - // here in order to get good results. - // ----------------------------------------------------- - int angles[3]; - angles[0] = (int)(in.direction.x * 100); - angles[1] = (int)(in.direction.y * 100); - angles[2] = (int)(in.direction.z * 100); + switch (in.type) { + case Animator::ROTATION: { + // ----------------------------------------------------- + // find out how long a full rotation will take + // This is the least common multiple of 360.f and all + // three euler angles. Although we'll surely find a + // possible multiple (haha) it could be somewhat large + // for our purposes. So we need to modify the angles + // here in order to get good results. + // ----------------------------------------------------- + int angles[3]; + angles[0] = (int)(in.direction.x * 100); + angles[1] = (int)(in.direction.y * 100); + angles[2] = (int)(in.direction.z * 100); - angles[0] %= 360; - angles[1] %= 360; - angles[2] %= 360; + angles[0] %= 360; + angles[1] %= 360; + angles[2] %= 360; - if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) { - FindSuitableMultiple(angles[0]); - FindSuitableMultiple(angles[1]); - FindSuitableMultiple(angles[2]); - } + if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) { + FindSuitableMultiple(angles[0]); + FindSuitableMultiple(angles[1]); + FindSuitableMultiple(angles[2]); + } - int lcm = 360; + int lcm = 360; - if (angles[0]) - lcm = Math::lcm(lcm, angles[0]); + if (angles[0]) + lcm = Math::lcm(lcm, angles[0]); - if (angles[1]) - lcm = Math::lcm(lcm, angles[1]); + if (angles[1]) + lcm = Math::lcm(lcm, angles[1]); - if (angles[2]) - lcm = Math::lcm(lcm, angles[2]); + if (angles[2]) + lcm = Math::lcm(lcm, angles[2]); - if (360 == lcm) - break; + if (360 == lcm) + break; + // find out how many time units we'll need for the finest + // track (in seconds) - this defines the number of output + // keys (fps * seconds) + float max = 0.f; + if (angles[0]) + max = (float)lcm / angles[0]; + if (angles[1]) + max = std::max(max, (float)lcm / angles[1]); + if (angles[2]) + max = std::max(max, (float)lcm / angles[2]); - // find out how many time units we'll need for the finest - // track (in seconds) - this defines the number of output - // keys (fps * seconds) - float max = 0.f; - if (angles[0]) - max = (float)lcm / angles[0]; - if (angles[1]) - max = std::max(max, (float)lcm / angles[1]); - if (angles[2]) - max = std::max(max, (float)lcm / angles[2]); + anim->mNumRotationKeys = (unsigned int)(max * fps); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; - anim->mNumRotationKeys = (unsigned int)(max * fps); - anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + // begin with a zero angle + aiVector3D angle; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + // build the quaternion for the given euler angles + aiQuatKey &q = anim->mRotationKeys[i]; - // begin with a zero angle - aiVector3D angle; - for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { - // build the quaternion for the given euler angles - aiQuatKey &q = anim->mRotationKeys[i]; + q.mValue = aiQuaternion(angle.x, angle.y, angle.z); + q.mTime = (double)i; - q.mValue = aiQuaternion(angle.x, angle.y, angle.z); - q.mTime = (double)i; + // increase the angle + angle += in.direction; + } - // increase the angle - angle += in.direction; - } + // This animation is repeated and repeated ... + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + } break; - // This animation is repeated and repeated ... - anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; - } break; + case Animator::FLY_CIRCLE: { + // ----------------------------------------------------- + // Find out how much time we'll need to perform a + // full circle. + // ----------------------------------------------------- + const double seconds = (1. / in.speed) / 1000.; + const double tdelta = 1000. / fps; - case Animator::FLY_CIRCLE: { - // ----------------------------------------------------- - // Find out how much time we'll need to perform a - // full circle. - // ----------------------------------------------------- - const double seconds = (1. / in.speed) / 1000.; - const double tdelta = 1000. / fps; + anim->mNumPositionKeys = (unsigned int)(fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - anim->mNumPositionKeys = (unsigned int)(fps * seconds); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + // from Irrlicht, what else should we do than copying it? + aiVector3D vecU, vecV; + if (in.direction.y) { + vecV = aiVector3D(50, 0, 0) ^ in.direction; + } else + vecV = aiVector3D(0, 50, 00) ^ in.direction; + vecV.Normalize(); + vecU = (vecV ^ in.direction).Normalize(); - // from Irrlicht, what else should we do than copying it? - aiVector3D vecU, vecV; - if (in.direction.y) { - vecV = aiVector3D(50, 0, 0) ^ in.direction; - } else - vecV = aiVector3D(0, 50, 00) ^ in.direction; - vecV.Normalize(); - vecU = (vecV ^ in.direction).Normalize(); + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; - // build the output keys - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - aiVectorKey &key = anim->mPositionKeys[i]; - key.mTime = i * tdelta; + const ai_real t = (ai_real)(in.speed * key.mTime); + key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t))); + } - const ai_real t = (ai_real)(in.speed * key.mTime); - key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t))); - } + // This animation is repeated and repeated ... + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + } break; - // This animation is repeated and repeated ... - anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; - } break; + case Animator::FLY_STRAIGHT: { + anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT); + const double seconds = in.timeForWay / 1000.; + const double tdelta = 1000. / fps; - case Animator::FLY_STRAIGHT: { - anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT); - const double seconds = in.timeForWay / 1000.; - const double tdelta = 1000. / fps; + anim->mNumPositionKeys = (unsigned int)(fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - anim->mNumPositionKeys = (unsigned int)(fps * seconds); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + aiVector3D diff = in.direction - in.circleCenter; + const ai_real lengthOfWay = diff.Length(); + diff.Normalize(); - aiVector3D diff = in.direction - in.circleCenter; - const ai_real lengthOfWay = diff.Length(); - diff.Normalize(); + const double timeFactor = lengthOfWay / in.timeForWay; - const double timeFactor = lengthOfWay / in.timeForWay; + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; + key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime); + } + } break; - // build the output keys - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - aiVectorKey &key = anim->mPositionKeys[i]; - key.mTime = i * tdelta; - key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime); - } - } break; + case Animator::FOLLOW_SPLINE: { + // repeat outside the defined time range + anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; + const int size = (int)in.splineKeys.size(); + if (!size) { + // We have no point in the spline. That's bad. Really bad. + ASSIMP_LOG_WARN("IRR: Spline animators with no points defined"); - case Animator::FOLLOW_SPLINE: { - // repeat outside the defined time range - anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT; - const int size = (int)in.splineKeys.size(); - if (!size) { - // We have no point in the spline. That's bad. Really bad. - ASSIMP_LOG_WARN("IRR: Spline animators with no points defined"); + delete anim; + anim = nullptr; + break; + } else if (size == 1) { + // We have just one point in the spline so we don't need the full calculation + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - delete anim; - anim = nullptr; - break; - } else if (size == 1) { - // We have just one point in the spline so we don't need the full calculation - anim->mNumPositionKeys = 1; - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue; + anim->mPositionKeys[0].mTime = 0.f; + break; + } - anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue; - anim->mPositionKeys[0].mTime = 0.f; - break; - } + unsigned int ticksPerFull = 15; + anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - unsigned int ticksPerFull = 15; - anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + aiVectorKey &key = anim->mPositionKeys[i]; - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - aiVectorKey &key = anim->mPositionKeys[i]; + const ai_real dt = (i * in.speed * ai_real(0.001)); + const ai_real u = dt - std::floor(dt); + const int idx = (int)std::floor(dt) % size; - const ai_real dt = (i * in.speed * ai_real(0.001)); - const ai_real u = dt - std::floor(dt); - const int idx = (int)std::floor(dt) % size; + // get the 4 current points to evaluate the spline + const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue; + const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue; + const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue; + const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue; - // get the 4 current points to evaluate the spline - const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue; - const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue; - const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue; - const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue; + // compute polynomials + const ai_real u2 = u * u; + const ai_real u3 = u2 * 2; - // compute polynomials - const ai_real u2 = u * u; - const ai_real u3 = u2 * 2; + const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0); + const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3; + const ai_real h3 = u3 - ai_real(2.0) * u3; + const ai_real h4 = u3 - u2; - const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0); - const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3; - const ai_real h3 = u3 - ai_real(2.0) * u3; - const ai_real h4 = u3 - u2; + // compute the spline tangents + const aiVector3D t1 = (p2 - p0) * in.tightness; + aiVector3D t2 = (p3 - p1) * in.tightness; - // compute the spline tangents - const aiVector3D t1 = (p2 - p0) * in.tightness; - aiVector3D t2 = (p3 - p1) * in.tightness; + // and use them to get the interpolated point + t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2); - // and use them to get the interpolated point - t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2); - - // build a simple translation matrix from it - key.mValue = t2; - key.mTime = (double)i; - } - } break; - default: - // UNKNOWN , OTHER - break; - }; - if (anim) { - anims.push_back(anim); - ++total; - } - } + // build a simple translation matrix from it + key.mValue = t2; + key.mTime = (double)i; + } + } break; + default: + // UNKNOWN , OTHER + break; + }; + if (anim) { + anims.push_back(anim); + ++total; + } + } } // ------------------------------------------------------------------------------------------------ // This function is maybe more generic than we'd need it here void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis = aiVector3D(0.f, 0.f, -1.f)) { - if (nullptr == mat) { - return; - } + if (nullptr == mat) { + return; + } // Check whether there are texture properties defined - setup - // the desired texture mapping mode for all of them and ignore - // all UV settings we might encounter. WE HAVE NO UVS! + // the desired texture mapping mode for all of them and ignore + // all UV settings we might encounter. WE HAVE NO UVS! - std::vector p; - p.reserve(mat->mNumProperties + 1); + std::vector p; + p.reserve(mat->mNumProperties + 1); - for (unsigned int i = 0; i < mat->mNumProperties; ++i) { - aiMaterialProperty *prop = mat->mProperties[i]; - if (!::strcmp(prop->mKey.data, "$tex.file")) { - // Setup the mapping key - aiMaterialProperty *m = new aiMaterialProperty(); - m->mKey.Set("$tex.mapping"); - m->mIndex = prop->mIndex; - m->mSemantic = prop->mSemantic; - m->mType = aiPTI_Integer; + for (unsigned int i = 0; i < mat->mNumProperties; ++i) { + aiMaterialProperty *prop = mat->mProperties[i]; + if (!::strcmp(prop->mKey.data, "$tex.file")) { + // Setup the mapping key + aiMaterialProperty *m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapping"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Integer; - m->mDataLength = 4; - m->mData = new char[4]; - *((int *)m->mData) = mode; + m->mDataLength = 4; + m->mData = new char[4]; + *((int *)m->mData) = mode; - p.push_back(prop); - p.push_back(m); + p.push_back(prop); + p.push_back(m); - // Setup the mapping axis - if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) { - m = new aiMaterialProperty(); - m->mKey.Set("$tex.mapaxis"); - m->mIndex = prop->mIndex; - m->mSemantic = prop->mSemantic; - m->mType = aiPTI_Float; + // Setup the mapping axis + if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) { + m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapaxis"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Float; - m->mDataLength = 12; - m->mData = new char[12]; - *((aiVector3D *)m->mData) = axis; - p.push_back(m); - } - } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) { - delete mat->mProperties[i]; - } else - p.push_back(prop); - } + m->mDataLength = sizeof(aiVector3D); + m->mData = new char[m->mDataLength]; + *((aiVector3D *)m->mData) = axis; + p.push_back(m); + } + } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) { + delete mat->mProperties[i]; + } else + p.push_back(prop); + } - if (p.empty()) return; + if (p.empty()) return; - // rebuild the output array - if (p.size() > mat->mNumAllocated) { - delete[] mat->mProperties; - mat->mProperties = new aiMaterialProperty *[p.size() * 2]; + // rebuild the output array + if (p.size() > mat->mNumAllocated) { + delete[] mat->mProperties; + mat->mProperties = new aiMaterialProperty *[p.size() * 2]; - mat->mNumAllocated = static_cast(p.size() * 2); - } - mat->mNumProperties = (unsigned int)p.size(); - ::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties); + mat->mNumAllocated = static_cast(p.size() * 2); + } + mat->mNumProperties = (unsigned int)p.size(); + ::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties); } // ------------------------------------------------------------------------------------------------ void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene, - BatchLoader &batch, - std::vector &meshes, - std::vector &anims, - std::vector &attach, - std::vector &materials, - unsigned int &defMatIdx) { - unsigned int oldMeshSize = (unsigned int)meshes.size(); - //unsigned int meshTrafoAssign = 0; + BatchLoader &batch, + std::vector &meshes, + std::vector &anims, + std::vector &attach, + std::vector &materials, + unsigned int &defMatIdx) { + unsigned int oldMeshSize = (unsigned int)meshes.size(); + // unsigned int meshTrafoAssign = 0; - // Now determine the type of the node - switch (root->type) { - case Node::ANIMMESH: - case Node::MESH: { - if (!root->meshPath.length()) - break; + // Now determine the type of the node + switch (root->type) { + case Node::ANIMMESH: + case Node::MESH: { + if (!root->meshPath.length()) + break; - // Get the loaded mesh from the scene and add it to - // the list of all scenes to be attached to the - // graph we're currently building - aiScene *localScene = batch.GetImport(root->id); - if (!localScene) { - ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath); - break; - } - attach.emplace_back(localScene, rootOut); + // Get the loaded mesh from the scene and add it to + // the list of all scenes to be attached to the + // graph we're currently building + aiScene *localScene = batch.GetImport(root->id); + if (!localScene) { + ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath); + break; + } + attach.emplace_back(localScene, rootOut); - // Now combine the material we've loaded for this mesh - // with the real materials we got from the file. As we - // don't execute any pp-steps on the file, the numbers - // should be equal. If they are not, we can impossibly - // do this ... - if (root->materials.size() != (unsigned int)localScene->mNumMaterials) { - ASSIMP_LOG_WARN("IRR: Failed to match imported materials " - "with the materials found in the IRR scene file"); + // Now combine the material we've loaded for this mesh + // with the real materials we got from the file. As we + // don't execute any pp-steps on the file, the numbers + // should be equal. If they are not, we can impossibly + // do this ... + if (root->materials.size() != (unsigned int)localScene->mNumMaterials) { + ASSIMP_LOG_WARN("IRR: Failed to match imported materials " + "with the materials found in the IRR scene file"); - break; - } - for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) { - // Delete the old material, we don't need it anymore - delete localScene->mMaterials[i]; + break; + } + for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) { + // Delete the old material, we don't need it anymore + delete localScene->mMaterials[i]; - std::pair &src = root->materials[i]; - localScene->mMaterials[i] = src.first; - } + std::pair &src = root->materials[i]; + localScene->mMaterials[i] = src.first; + } - // NOTE: Each mesh should have exactly one material assigned, - // but we do it in a separate loop if this behavior changes - // in future. - for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) { - // Process material flags - aiMesh *mesh = localScene->mMeshes[i]; + // NOTE: Each mesh should have exactly one material assigned, + // but we do it in a separate loop if this behavior changes + // in future. + for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) { + // Process material flags + aiMesh *mesh = localScene->mMeshes[i]; - // If "trans_vertex_alpha" mode is enabled, search all vertex colors - // and check whether they have a common alpha value. This is quite - // often the case so we can simply extract it to a shared oacity - // value. - std::pair &src = root->materials[mesh->mMaterialIndex]; - aiMaterial *mat = (aiMaterial *)src.first; + // If "trans_vertex_alpha" mode is enabled, search all vertex colors + // and check whether they have a common alpha value. This is quite + // often the case so we can simply extract it to a shared oacity + // value. + std::pair &src = root->materials[mesh->mMaterialIndex]; + aiMaterial *mat = (aiMaterial *)src.first; - if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) { - bool bdo = true; - for (unsigned int a = 1; a < mesh->mNumVertices; ++a) { + if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) { + bool bdo = true; + for (unsigned int a = 1; a < mesh->mNumVertices; ++a) { - if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) { - bdo = false; - break; - } - } - if (bdo) { - ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity"); + if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) { + bdo = false; + break; + } + } + if (bdo) { + ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity"); - for (unsigned int a = 0; a < mesh->mNumVertices; ++a) - mesh->mColors[0][a].a = 1.f; + for (unsigned int a = 0; a < mesh->mNumVertices; ++a) + mesh->mColors[0][a].a = 1.f; - mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY); - } - } + mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY); + } + } - // If we have a second texture coordinate set and a second texture - // (either light-map, normal-map, 2layered material) we need to - // setup the correct UV index for it. The texture can either - // be diffuse (light-map & 2layer) or a normal map (normal & parallax) - if (mesh->HasTextureCoords(1)) { + // If we have a second texture coordinate set and a second texture + // (either light-map, normal-map, 2layered material) we need to + // setup the correct UV index for it. The texture can either + // be diffuse (light-map & 2layer) or a normal map (normal & parallax) + if (mesh->HasTextureCoords(1)) { - int idx = 1; - if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) { - mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0)); - } else if (src.second & AI_IRRMESH_MAT_normalmap_solid) { - mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); - } - } - } - } break; + int idx = 1; + if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0)); + } else if (src.second & AI_IRRMESH_MAT_normalmap_solid) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); + } + } + } + } break; - case Node::LIGHT: - case Node::CAMERA: + case Node::LIGHT: + case Node::CAMERA: - // We're already finished with lights and cameras - break; + // We're already finished with lights and cameras + break; - case Node::SPHERE: { - // Generate the sphere model. Our input parameter to - // the sphere generation algorithm is the number of - // subdivisions of each triangle - but here we have - // the number of polygons on a specific axis. Just - // use some hard-coded limits to approximate this ... - unsigned int mul = root->spherePolyCountX * root->spherePolyCountY; - if (mul < 100) - mul = 2; - else if (mul < 300) - mul = 3; - else - mul = 4; + case Node::SPHERE: { + // Generate the sphere model. Our input parameter to + // the sphere generation algorithm is the number of + // subdivisions of each triangle - but here we have + // the number of polygons on a specific axis. Just + // use some hard-coded limits to approximate this ... + unsigned int mul = root->spherePolyCountX * root->spherePolyCountY; + if (mul < 100) + mul = 2; + else if (mul < 300) + mul = 3; + else + mul = 4; - meshes.push_back(StandardShapes::MakeMesh(mul, - &StandardShapes::MakeSphere)); + meshes.push_back(StandardShapes::MakeMesh(mul, + &StandardShapes::MakeSphere)); - // Adjust scaling - root->scaling *= root->sphereRadius / 2; + // Adjust scaling + root->scaling *= root->sphereRadius / 2; - // Copy one output material - CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + // Copy one output material + CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); - // Now adjust this output material - if there is a first texture - // set, setup spherical UV mapping around the Y axis. - SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE); - } break; + // Now adjust this output material - if there is a first texture + // set, setup spherical UV mapping around the Y axis. + SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE); + } break; - case Node::CUBE: { - // Generate an unit cube first - meshes.push_back(StandardShapes::MakeMesh( - &StandardShapes::MakeHexahedron)); + case Node::CUBE: { + // Generate an unit cube first + meshes.push_back(StandardShapes::MakeMesh( + &StandardShapes::MakeHexahedron)); - // Adjust scaling - root->scaling *= root->sphereRadius; + // Adjust scaling + root->scaling *= root->sphereRadius; - // Copy one output material - CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + // Copy one output material + CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); - // Now adjust this output material - if there is a first texture - // set, setup cubic UV mapping - SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX); - } break; + // Now adjust this output material - if there is a first texture + // set, setup cubic UV mapping + SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX); + } break; - case Node::SKYBOX: { - // A sky-box is defined by six materials - if (root->materials.size() < 6) { - ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox"); - break; - } + case Node::SKYBOX: { + // A sky-box is defined by six materials + if (root->materials.size() < 6) { + ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox"); + break; + } - // copy those materials and generate 6 meshes for our new sky-box - materials.reserve(materials.size() + 6); - for (unsigned int i = 0; i < 6; ++i) - materials.insert(materials.end(), root->materials[i].first); + // copy those materials and generate 6 meshes for our new sky-box + materials.reserve(materials.size() + 6); + for (unsigned int i = 0; i < 6; ++i) + materials.insert(materials.end(), root->materials[i].first); - BuildSkybox(meshes, materials); + BuildSkybox(meshes, materials); - // ************************************************************* - // Skyboxes will require a different code path for rendering, - // so there must be a way for the user to add special support - // for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node. - // ************************************************************* - root->name = "IRR.SkyBox_" + root->name; - ASSIMP_LOG_INFO("IRR: Loading skybox, this will " - "require special handling to be displayed correctly"); - } break; + // ************************************************************* + // Skyboxes will require a different code path for rendering, + // so there must be a way for the user to add special support + // for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node. + // ************************************************************* + root->name = "IRR.SkyBox_" + root->name; + ASSIMP_LOG_INFO("IRR: Loading skybox, this will " + "require special handling to be displayed correctly"); + } break; - case Node::TERRAIN: { - // to support terrains, we'd need to have a texture decoder - ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN"); - } break; - default: - // DUMMY - break; - }; + case Node::TERRAIN: { + // to support terrains, we'd need to have a texture decoder + ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN"); + } break; + default: + // DUMMY + break; + }; - // Check whether we added a mesh (or more than one ...). In this case - // we'll also need to attach it to the node - if (oldMeshSize != (unsigned int)meshes.size()) { + // Check whether we added a mesh (or more than one ...). In this case + // we'll also need to attach it to the node + if (oldMeshSize != (unsigned int)meshes.size()) { - rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize; - rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes]; + rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize; + rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes]; - for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) { - rootOut->mMeshes[a] = oldMeshSize + a; - } - } + for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) { + rootOut->mMeshes[a] = oldMeshSize + a; + } + } - // Setup the name of this node - rootOut->mName.Set(root->name); + // Setup the name of this node + rootOut->mName.Set(root->name); - // Now compute the final local transformation matrix of the - // node from the given translation, rotation and scaling values. - // (the rotation is given in Euler angles, XYZ order) - //std::swap((float&)root->rotation.z,(float&)root->rotation.y); - rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation)); + // Now compute the final local transformation matrix of the + // node from the given translation, rotation and scaling values. + // (the rotation is given in Euler angles, XYZ order) + // std::swap((float&)root->rotation.z,(float&)root->rotation.y); + rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation)); - // apply scaling - aiMatrix4x4 &mat = rootOut->mTransformation; - mat.a1 *= root->scaling.x; - mat.b1 *= root->scaling.x; - mat.c1 *= root->scaling.x; - mat.a2 *= root->scaling.y; - mat.b2 *= root->scaling.y; - mat.c2 *= root->scaling.y; - mat.a3 *= root->scaling.z; - mat.b3 *= root->scaling.z; - mat.c3 *= root->scaling.z; + // apply scaling + aiMatrix4x4 &mat = rootOut->mTransformation; + mat.a1 *= root->scaling.x; + mat.b1 *= root->scaling.x; + mat.c1 *= root->scaling.x; + mat.a2 *= root->scaling.y; + mat.b2 *= root->scaling.y; + mat.c2 *= root->scaling.y; + mat.a3 *= root->scaling.z; + mat.b3 *= root->scaling.z; + mat.c3 *= root->scaling.z; - // apply translation - mat.a4 += root->position.x; - mat.b4 += root->position.y; - mat.c4 += root->position.z; + // apply translation + mat.a4 += root->position.x; + mat.b4 += root->position.y; + mat.c4 += root->position.z; - // now compute animations for the node - ComputeAnimations(root, rootOut, anims); + // now compute animations for the node + ComputeAnimations(root, rootOut, anims); - // Add all children recursively. First allocate enough storage - // for them, then call us again - rootOut->mNumChildren = (unsigned int)root->children.size(); - if (rootOut->mNumChildren) { + // Add all children recursively. First allocate enough storage + // for them, then call us again + rootOut->mNumChildren = (unsigned int)root->children.size(); + if (rootOut->mNumChildren) { - rootOut->mChildren = new aiNode *[rootOut->mNumChildren]; - for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) { + rootOut->mChildren = new aiNode *[rootOut->mNumChildren]; + for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) { - aiNode *node = rootOut->mChildren[i] = new aiNode(); - node->mParent = rootOut; - GenerateGraph(root->children[i], node, scene, batch, meshes, - anims, attach, materials, defMatIdx); - } - } + aiNode *node = rootOut->mChildren[i] = new aiNode(); + node->mParent = rootOut; + GenerateGraph(root->children[i], node, scene, batch, meshes, + anims, attach, materials, defMatIdx); + } + } +} + +void IRRImporter::ParseNodeAttributes(pugi::xml_node &attributesNode, IRRImporter::Node *nd, BatchLoader &batch) { + ai_assert(!ASSIMP_stricmp(attributesNode.name(), "attributes")); // Node must be + ai_assert(nd != nullptr); // dude + + // Big switch statement that tests for various tags inside + // and applies them to nd + // I don't believe nodes have boolean attributes + for (pugi::xml_node &attribute : attributesNode.children()) { + if (attribute.type() != pugi::node_element) continue; + if (!ASSIMP_stricmp(attribute.name(), "vector3d")) { // + VectorProperty prop; + ReadVectorProperty(prop, attribute); + if (prop.name == "Position") { + nd->position = prop.value; + } else if (prop.name == "Rotation") { + nd->rotation = prop.value; + } else if (prop.name == "Scale") { + nd->scaling = prop.value; + } else if (Node::CAMERA == nd->type) { + aiCamera *cam = cameras.back(); + if (prop.name == "Target") { + cam->mLookAt = prop.value; + } else if (prop.name == "UpVector") { + cam->mUp = prop.value; + } + } + } else if (!ASSIMP_stricmp(attribute.name(), "float")) { // + FloatProperty prop; + ReadFloatProperty(prop, attribute); + if (prop.name == "FramesPerSecond" && Node::ANIMMESH == nd->type) { + nd->framesPerSecond = prop.value; + } else if (Node::CAMERA == nd->type) { + /* This is the vertical, not the horizontal FOV. + * We need to compute the right FOV from the + * screen aspect which we don't know yet. + */ + if (prop.name == "Fovy") { + cameras.back()->mHorizontalFOV = prop.value; + } else if (prop.name == "Aspect") { + cameras.back()->mAspect = prop.value; + } else if (prop.name == "ZNear") { + cameras.back()->mClipPlaneNear = prop.value; + } else if (prop.name == "ZFar") { + cameras.back()->mClipPlaneFar = prop.value; + } + } else if (Node::LIGHT == nd->type) { + /* Additional light information + */ + if (prop.name == "Attenuation") { + lights.back()->mAttenuationLinear = prop.value; + } else if (prop.name == "OuterCone") { + lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value); + } else if (prop.name == "InnerCone") { + lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value); + } + } + // radius of the sphere to be generated - + // or alternatively, size of the cube + else if ((Node::SPHERE == nd->type && prop.name == "Radius") || + (Node::CUBE == nd->type && prop.name == "Size")) { + nd->sphereRadius = prop.value; + } + } else if (!ASSIMP_stricmp(attribute.name(), "int")) { // + // Only sphere nodes make use of integer attributes + if (Node::SPHERE == nd->type) { + IntProperty prop; + ReadIntProperty(prop, attribute); + if (prop.name == "PolyCountX") { + nd->spherePolyCountX = prop.value; + } else if (prop.name == "PolyCountY") { + nd->spherePolyCountY = prop.value; + } + } + } else if (!ASSIMP_stricmp(attribute.name(), "string") || !ASSIMP_stricmp(attribute.name(), "enum")) { // or < enum /> + StringProperty prop; + ReadStringProperty(prop, attribute); + if (prop.value.length() == 0) continue; // skip empty strings + if (prop.name == "Name") { + nd->name = prop.value; + + /* If we're either a camera or a light source + * we need to update the name in the aiLight/ + * aiCamera structure, too. + */ + if (Node::CAMERA == nd->type) { + cameras.back()->mName.Set(prop.value); + } else if (Node::LIGHT == nd->type) { + lights.back()->mName.Set(prop.value); + } + } else if (Node::LIGHT == nd->type && "LightType" == prop.name) { + if (prop.value == "Spot") + lights.back()->mType = aiLightSource_SPOT; + else if (prop.value == "Point") + lights.back()->mType = aiLightSource_POINT; + else if (prop.value == "Directional") + lights.back()->mType = aiLightSource_DIRECTIONAL; + else { + // We won't pass the validation with aiLightSourceType_UNDEFINED, + // so we remove the light and replace it with a silly dummy node + delete lights.back(); + lights.pop_back(); + nd->type = Node::DUMMY; + + ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value); + } + } else if ((prop.name == "Mesh" && Node::MESH == nd->type) || + Node::ANIMMESH == nd->type) { + /* This is the file name of the mesh - either + * animated or not. We need to make sure we setup + * the correct post-processing settings here. + */ + unsigned int pp = 0; + BatchLoader::PropertyMap map; + + /* If the mesh is a static one remove all animations from the impor data + */ + if (Node::ANIMMESH != nd->type) { + pp |= aiProcess_RemoveComponent; + SetGenericProperty(map.ints, AI_CONFIG_PP_RVC_FLAGS, + aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS); + } + + /* TODO: maybe implement the protection against recursive + * loading calls directly in BatchLoader? The current + * implementation is not absolutely safe. A LWS and an IRR + * file referencing each other *could* cause the system to + * recurse forever. + */ + + const std::string extension = GetExtension(prop.value); + if ("irr" == extension) { + ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively"); + } else { + nd->id = batch.AddLoadRequest(prop.value, pp, &map); + nd->meshPath = prop.value; + } + } + } + } +} + +void IRRImporter::ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd) { + Animator *curAnim = nullptr; + // Make empty animator + nd->animators.emplace_back(); + curAnim = &nd->animators.back(); // Push it back + pugi::xml_node attributes = animatorNode.child("attributes"); + if (!attributes) { + ASSIMP_LOG_WARN("Animator node does not contain attributes. "); + return; + } + + for (pugi::xml_node attrib : attributes.children()) { + // XML may contain useless noes like CDATA + if (!ASSIMP_stricmp(attrib.name(), "vector3d")) { + VectorProperty prop; + ReadVectorProperty(prop, attrib); + + if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") { + // We store the rotation euler angles in 'direction' + curAnim->direction = prop.value; + } else if (curAnim->type == Animator::FOLLOW_SPLINE) { + // Check whether the vector follows the PointN naming scheme, + // here N is the ONE-based index of the point + if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") { + // Add a new key to the list + curAnim->splineKeys.emplace_back(); + aiVectorKey &key = curAnim->splineKeys.back(); + + // and parse its properties + key.mValue = prop.value; + key.mTime = strtoul10(&prop.name[5]); + } + } else if (curAnim->type == Animator::FLY_CIRCLE) { + if (prop.name == "Center") { + curAnim->circleCenter = prop.value; + } else if (prop.name == "Direction") { + curAnim->direction = prop.value; + + // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1 + if (curAnim->direction == aiVector3D()) { + curAnim->direction = aiVector3D(0.f, 1.f, 0.f); + } else + curAnim->direction.Normalize(); + } + } else if (curAnim->type == Animator::FLY_STRAIGHT) { + if (prop.name == "Start") { + // We reuse the field here + curAnim->circleCenter = prop.value; + } else if (prop.name == "End") { + // We reuse the field here + curAnim->direction = prop.value; + } + } + + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) { + } else if (!ASSIMP_stricmp(attrib.name(), "bool")) { + BoolProperty prop; + ReadBoolProperty(prop, attrib); + + if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") { + curAnim->loop = prop.value; + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) { + } else if (!ASSIMP_stricmp(attrib.name(), "float")) { + FloatProperty prop; + ReadFloatProperty(prop, attrib); + + // The speed property exists for several animators + if (prop.name == "Speed") { + curAnim->speed = prop.value; + } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") { + curAnim->circleRadius = prop.value; + } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") { + curAnim->tightness = prop.value; + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) { + } else if (!ASSIMP_stricmp(attrib.name(), "int")) { + IntProperty prop; + ReadIntProperty(prop, attrib); + + if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") { + curAnim->timeForWay = prop.value; + } + //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) { + } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) { + StringProperty prop; + ReadStringProperty(prop, attrib); + + if (prop.name == "Type") { + // type of the animator + if (prop.value == "rotation") { + curAnim->type = Animator::ROTATION; + } else if (prop.value == "flyCircle") { + curAnim->type = Animator::FLY_CIRCLE; + } else if (prop.value == "flyStraight") { + curAnim->type = Animator::FLY_CIRCLE; + } else if (prop.value == "followSpline") { + curAnim->type = Animator::FOLLOW_SPLINE; + } else { + ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value); + + curAnim->type = Animator::UNKNOWN; + } + } + } + } +} + +IRRImporter::Node *IRRImporter::ParseNode(pugi::xml_node &node, BatchLoader &batch) { + // Parse tags. + // tags have various types + // tags can contain , + // they can also contain other tags, (and can reference other files as well?) + // *********************************************************************** + /* What we're going to do with the node depends + * on its type: + * + * "mesh" - Load a mesh from an external file + * "cube" - Generate a cube + * "skybox" - Generate a skybox + * "light" - A light source + * "sphere" - Generate a sphere mesh + * "animatedMesh" - Load an animated mesh from an external file + * and join its animation channels with ours. + * "empty" - A dummy node + * "camera" - A camera + * "terrain" - a terrain node (data comes from a heightmap) + * "billboard", "" + * + * Each of these nodes can be animated and all can have multiple + * materials assigned (except lights, cameras and dummies, of course). + * Said materials and animators are all collected at the bottom + */ + // *********************************************************************** + Node *nd; + pugi::xml_attribute nodeTypeAttrib = node.attribute("type"); + if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "mesh") || !ASSIMP_stricmp(nodeTypeAttrib.value(), "octTree")) { + // OctTree's and meshes are treated equally + nd = new Node(Node::MESH); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "cube")) { + nd = new Node(Node::CUBE); + guessedMeshCnt += 1; // Cube is only one mesh + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "skybox")) { + nd = new Node(Node::SKYBOX); + guessedMeshCnt += 6; // Skybox is a box, with 6 meshes? + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "camera")) { + nd = new Node(Node::CAMERA); + // Setup a temporary name for the camera + aiCamera *cam = new aiCamera(); + cam->mName.Set(nd->name); + cameras.push_back(cam); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "light")) { + nd = new Node(Node::LIGHT); + // Setup a temporary name for the light + aiLight *cam = new aiLight(); + cam->mName.Set(nd->name); + lights.push_back(cam); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "sphere")) { + nd = new Node(Node::SPHERE); + guessedMeshCnt += 1; + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "animatedMesh")) { + nd = new Node(Node::ANIMMESH); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "empty")) { + nd = new Node(Node::DUMMY); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "terrain")) { + nd = new Node(Node::TERRAIN); + } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "billBoard")) { + // We don't support billboards, so ignore them + ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp"); + nd = new Node(Node::DUMMY); + } else { + ASSIMP_LOG_WARN("IRR: Found unknown node: ", nodeTypeAttrib.value()); + + /* We skip the contents of nodes we don't know. + * We parse the transformation and all animators + * and skip the rest. + */ + nd = new Node(Node::DUMMY); + } + + // TODO: consolidate all into one loop + for (pugi::xml_node subNode : node.children()) { + // Collect node attributes first + if (!ASSIMP_stricmp(subNode.name(), "attributes")) { + ParseNodeAttributes(subNode, nd, batch); // Parse attributes into this node + } else if (!ASSIMP_stricmp(subNode.name(), "animators")) { + // Then parse any animators + // All animators should contain an tag + + // This is an animation path - add a new animator + // to the list. + ParseAnimators(subNode, nd); // Function modifies nd's animator vector + guessedAnimCnt += 1; + } + + // Then parse any materials + // Materials are available to almost all node types + if (nd->type != Node::DUMMY) { + if (!ASSIMP_stricmp(subNode.name(), "materials")) { + // Parse material description directly + // Each material should contain an node + // with everything specified + nd->materials.emplace_back(); + std::pair &p = nd->materials.back(); + p.first = ParseMaterial(subNode, p.second); + guessedMatCnt += 1; + } + } + } + + // Then parse any child nodes + // Attach the newly created node to the scene-graph + for (pugi::xml_node child : node.children()) { + if (!ASSIMP_stricmp(child.name(), "node")) { // Is a child node + Node *childNd = ParseNode(child, batch); // Repeat this function for all children + nd->children.push_back(childNd); + }; + } + + // Return fully specified node + return nd; } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { - std::unique_ptr file(pIOHandler->Open(pFile)); - - // Check whether we can read from the file - if (file.get() == nullptr) { + std::unique_ptr file(pIOHandler->Open(pFile)); + // Check whether we can read from the file + if (file == nullptr) { throw DeadlyImportError("Failed to open IRR file ", pFile); - } + } - // Construct the irrXML parser - XmlParser st; - if (!st.parse( file.get() )) { + // Construct the irrXML parser + XmlParser st; + if (!st.parse(file.get())) { throw DeadlyImportError("XML parse error while loading IRR file ", pFile); } - pugi::xml_node rootElement = st.getRootNode(); + pugi::xml_node documentRoot = st.getRootNode(); - // The root node of the scene - Node *root = new Node(Node::DUMMY); - root->parent = nullptr; - root->name = ""; + // The root node of the scene + Node *root = new Node(Node::DUMMY); + root->parent = nullptr; + root->name = ""; - // Current node parent - Node *curParent = root; + // Batch loader used to load external models + BatchLoader batch(pIOHandler); + // batch.SetBasePath(pFile); - // Scene-graph node we're currently working on - Node *curNode = nullptr; + cameras.reserve(1); // Probably only one camera in entire scene + lights.reserve(5); - // List of output cameras - std::vector cameras; + this->guessedAnimCnt = 0; + this->guessedMeshCnt = 0; + this->guessedMatCnt = 0; - // List of output lights - std::vector lights; + // Parse the XML + // Find the scene root from document root. + const pugi::xml_node &sceneRoot = documentRoot.child("irr_scene"); + if (!sceneRoot) { + delete root; + throw new DeadlyImportError("IRR: not found in file"); + } + for (pugi::xml_node &child : sceneRoot.children()) { + // XML elements are either nodes, animators, attributes, or materials + if (!ASSIMP_stricmp(child.name(), "node")) { + // Recursive collect subtree children + Node *nd = ParseNode(child, batch); + // Attach to root + root->children.push_back(nd); + } + } - // Batch loader used to load external models - BatchLoader batch(pIOHandler); - //batch.SetBasePath(pFile); + // Now iterate through all cameras and compute their final (horizontal) FOV + for (aiCamera *cam : cameras) { + // screen aspect could be missing + if (cam->mAspect) { + cam->mHorizontalFOV *= cam->mAspect; + } else { + ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV"); + } + } - cameras.reserve(5); - lights.reserve(5); + batch.LoadAll(); - bool inMaterials = false, inAnimator = false; - unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0; + // Allocate a temporary scene data structure + aiScene *tempScene = new aiScene(); + tempScene->mRootNode = new aiNode(); + tempScene->mRootNode->mName.Set(""); - // Parse the XML file + // Copy the cameras to the output array + if (!cameras.empty()) { + tempScene->mNumCameras = (unsigned int)cameras.size(); + tempScene->mCameras = new aiCamera *[tempScene->mNumCameras]; + ::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras); + } - //while (reader->read()) { - for (pugi::xml_node child : rootElement.children()) - switch (child.type()) { - case pugi::node_element: - if (!ASSIMP_stricmp(child.name(), "node")) { - // *********************************************************************** - /* What we're going to do with the node depends - * on its type: - * - * "mesh" - Load a mesh from an external file - * "cube" - Generate a cube - * "skybox" - Generate a skybox - * "light" - A light source - * "sphere" - Generate a sphere mesh - * "animatedMesh" - Load an animated mesh from an external file - * and join its animation channels with ours. - * "empty" - A dummy node - * "camera" - A camera - * "terrain" - a terrain node (data comes from a heightmap) - * "billboard", "" - * - * Each of these nodes can be animated and all can have multiple - * materials assigned (except lights, cameras and dummies, of course). - */ - // *********************************************************************** - //const char *sz = reader->getAttributeValueSafe("type"); - pugi::xml_attribute attrib = child.attribute("type"); - Node *nd; - if (!ASSIMP_stricmp(attrib.name(), "mesh") || !ASSIMP_stricmp(attrib.name(), "octTree")) { - // OctTree's and meshes are treated equally - nd = new Node(Node::MESH); - } else if (!ASSIMP_stricmp(attrib.name(), "cube")) { - nd = new Node(Node::CUBE); - ++guessedMeshCnt; - } else if (!ASSIMP_stricmp(attrib.name(), "skybox")) { - nd = new Node(Node::SKYBOX); - guessedMeshCnt += 6; - } else if (!ASSIMP_stricmp(attrib.name(), "camera")) { - nd = new Node(Node::CAMERA); + // Copy the light sources to the output array + if (!lights.empty()) { + tempScene->mNumLights = (unsigned int)lights.size(); + tempScene->mLights = new aiLight *[tempScene->mNumLights]; + ::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights); + } - // Setup a temporary name for the camera - aiCamera *cam = new aiCamera(); - cam->mName.Set(nd->name); - cameras.push_back(cam); - } else if (!ASSIMP_stricmp(attrib.name(), "light")) { - nd = new Node(Node::LIGHT); + // temporary data + std::vector anims; + std::vector materials; + std::vector attach; + std::vector meshes; - // Setup a temporary name for the light - aiLight *cam = new aiLight(); - cam->mName.Set(nd->name); - lights.push_back(cam); - } else if (!ASSIMP_stricmp(attrib.name(), "sphere")) { - nd = new Node(Node::SPHERE); - ++guessedMeshCnt; - } else if (!ASSIMP_stricmp(attrib.name(), "animatedMesh")) { - nd = new Node(Node::ANIMMESH); - } else if (!ASSIMP_stricmp(attrib.name(), "empty")) { - nd = new Node(Node::DUMMY); - } else if (!ASSIMP_stricmp(attrib.name(), "terrain")) { - nd = new Node(Node::TERRAIN); - } else if (!ASSIMP_stricmp(attrib.name(), "billBoard")) { - // We don't support billboards, so ignore them - ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp"); - nd = new Node(Node::DUMMY); - } else { - ASSIMP_LOG_WARN("IRR: Found unknown node: ", attrib.name()); + // try to guess how much storage we'll need + anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2)); + meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2)); + materials.reserve(guessedMatCnt + (guessedMatCnt >> 2)); - /* We skip the contents of nodes we don't know. - * We parse the transformation and all animators - * and skip the rest. - */ - nd = new Node(Node::DUMMY); - } + // Now process our scene-graph recursively: generate final + // meshes and generate animation channels for all nodes. + unsigned int defMatIdx = UINT_MAX; + GenerateGraph(root, tempScene->mRootNode, tempScene, + batch, meshes, anims, attach, materials, defMatIdx); - /* Attach the newly created node to the scene-graph - */ - curNode = nd; - nd->parent = curParent; - curParent->children.push_back(nd); - } else if (!ASSIMP_stricmp(child.name(), "materials")) { - inMaterials = true; - } else if (!ASSIMP_stricmp(child.name(), "animators")) { - inAnimator = true; - } else if (!ASSIMP_stricmp(child.name(), "attributes")) { - // We should have a valid node here - // FIX: no ... the scene root node is also contained in an attributes block - if (!curNode) { - continue; - } + if (!anims.empty()) { + tempScene->mNumAnimations = 1; + tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations]; + aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation(); - Animator *curAnim = nullptr; + // *********************************************************** + // This is only the global animation channel of the scene. + // If there are animated models, they will have separate + // animation channels in the scene. To display IRR scenes + // correctly, users will need to combine the global anim + // channel with all the local animations they want to play + // *********************************************************** + an->mName.Set("Irr_GlobalAnimChannel"); - // Materials can occur for nearly any type of node - if (inMaterials && curNode->type != Node::DUMMY) { - // This is a material description - parse it! - curNode->materials.emplace_back(); - std::pair &p = curNode->materials.back(); + // copy all node animation channels to the global channel + an->mNumChannels = (unsigned int)anims.size(); + an->mChannels = new aiNodeAnim *[an->mNumChannels]; + ::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels); + } + if (!meshes.empty()) { + // copy all meshes to the temporary scene + tempScene->mNumMeshes = (unsigned int)meshes.size(); + tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes]; + ::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *)); + } - p.first = ParseMaterial(p.second); - ++guessedMatCnt; - continue; - } else if (inAnimator) { - // This is an animation path - add a new animator - // to the list. - curNode->animators.emplace_back(); - curAnim = &curNode->animators.back(); + // Copy all materials to the output array + if (!materials.empty()) { + tempScene->mNumMaterials = (unsigned int)materials.size(); + tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials]; + ::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials); + } - ++guessedAnimCnt; - } + // Now merge all sub scenes and attach them to the correct + // attachment points in the scenegraph. + SceneCombiner::MergeScenes(&pScene, tempScene, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : + 0)); - /* Parse all elements in the attributes block - * and process them. - */ - // while (reader->read()) { - for (pugi::xml_node attrib : child.children()) { - if (attrib.type() == pugi::node_element) { - //if (reader->getNodeType() == EXN_ELEMENT) { - //if (!ASSIMP_stricmp(reader->getNodeName(), "vector3d")) { - if (!ASSIMP_stricmp(attrib.name(), "vector3d")) { - VectorProperty prop; - ReadVectorProperty(prop); + // If we have no meshes | no materials now set the INCOMPLETE + // scene flag. This is necessary if we failed to load all + // models from external files + if (!pScene->mNumMeshes || !pScene->mNumMaterials) { + ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE"); + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } - if (inAnimator) { - if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") { - // We store the rotation euler angles in 'direction' - curAnim->direction = prop.value; - } else if (curAnim->type == Animator::FOLLOW_SPLINE) { - // Check whether the vector follows the PointN naming scheme, - // here N is the ONE-based index of the point - if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") { - // Add a new key to the list - curAnim->splineKeys.emplace_back(); - aiVectorKey &key = curAnim->splineKeys.back(); - - // and parse its properties - key.mValue = prop.value; - key.mTime = strtoul10(&prop.name[5]); - } - } else if (curAnim->type == Animator::FLY_CIRCLE) { - if (prop.name == "Center") { - curAnim->circleCenter = prop.value; - } else if (prop.name == "Direction") { - curAnim->direction = prop.value; - - // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1 - if (curAnim->direction == aiVector3D()) { - curAnim->direction = aiVector3D(0.f, 1.f, 0.f); - } else - curAnim->direction.Normalize(); - } - } else if (curAnim->type == Animator::FLY_STRAIGHT) { - if (prop.name == "Start") { - // We reuse the field here - curAnim->circleCenter = prop.value; - } else if (prop.name == "End") { - // We reuse the field here - curAnim->direction = prop.value; - } - } - } else { - if (prop.name == "Position") { - curNode->position = prop.value; - } else if (prop.name == "Rotation") { - curNode->rotation = prop.value; - } else if (prop.name == "Scale") { - curNode->scaling = prop.value; - } else if (Node::CAMERA == curNode->type) { - aiCamera *cam = cameras.back(); - if (prop.name == "Target") { - cam->mLookAt = prop.value; - } else if (prop.name == "UpVector") { - cam->mUp = prop.value; - } - } - } - //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) { - } else if (!ASSIMP_stricmp(attrib.name(), "bool")) { - BoolProperty prop; - ReadBoolProperty(prop); - - if (inAnimator && curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") { - curAnim->loop = prop.value; - } - //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) { - } else if (!ASSIMP_stricmp(attrib.name(), "float")) { - FloatProperty prop; - ReadFloatProperty(prop); - - if (inAnimator) { - // The speed property exists for several animators - if (prop.name == "Speed") { - curAnim->speed = prop.value; - } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") { - curAnim->circleRadius = prop.value; - } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") { - curAnim->tightness = prop.value; - } - } else { - if (prop.name == "FramesPerSecond" && Node::ANIMMESH == curNode->type) { - curNode->framesPerSecond = prop.value; - } else if (Node::CAMERA == curNode->type) { - /* This is the vertical, not the horizontal FOV. - * We need to compute the right FOV from the - * screen aspect which we don't know yet. - */ - if (prop.name == "Fovy") { - cameras.back()->mHorizontalFOV = prop.value; - } else if (prop.name == "Aspect") { - cameras.back()->mAspect = prop.value; - } else if (prop.name == "ZNear") { - cameras.back()->mClipPlaneNear = prop.value; - } else if (prop.name == "ZFar") { - cameras.back()->mClipPlaneFar = prop.value; - } - } else if (Node::LIGHT == curNode->type) { - /* Additional light information - */ - if (prop.name == "Attenuation") { - lights.back()->mAttenuationLinear = prop.value; - } else if (prop.name == "OuterCone") { - lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value); - } else if (prop.name == "InnerCone") { - lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value); - } - } - // radius of the sphere to be generated - - // or alternatively, size of the cube - else if ((Node::SPHERE == curNode->type && prop.name == "Radius") || (Node::CUBE == curNode->type && prop.name == "Size")) { - - curNode->sphereRadius = prop.value; - } - } - //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) { - } else if (!ASSIMP_stricmp(attrib.name(), "int")) { - IntProperty prop; - ReadIntProperty(prop); - - if (inAnimator) { - if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") { - curAnim->timeForWay = prop.value; - } - } else { - // sphere polygon numbers in each direction - if (Node::SPHERE == curNode->type) { - - if (prop.name == "PolyCountX") { - curNode->spherePolyCountX = prop.value; - } else if (prop.name == "PolyCountY") { - curNode->spherePolyCountY = prop.value; - } - } - } - //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) { - } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) { - StringProperty prop; - ReadStringProperty(prop); - if (prop.value.length()) { - if (prop.name == "Name") { - curNode->name = prop.value; - - /* If we're either a camera or a light source - * we need to update the name in the aiLight/ - * aiCamera structure, too. - */ - if (Node::CAMERA == curNode->type) { - cameras.back()->mName.Set(prop.value); - } else if (Node::LIGHT == curNode->type) { - lights.back()->mName.Set(prop.value); - } - } else if (Node::LIGHT == curNode->type && "LightType" == prop.name) { - if (prop.value == "Spot") - lights.back()->mType = aiLightSource_SPOT; - else if (prop.value == "Point") - lights.back()->mType = aiLightSource_POINT; - else if (prop.value == "Directional") - lights.back()->mType = aiLightSource_DIRECTIONAL; - else { - // We won't pass the validation with aiLightSourceType_UNDEFINED, - // so we remove the light and replace it with a silly dummy node - delete lights.back(); - lights.pop_back(); - curNode->type = Node::DUMMY; - - ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value); - } - } else if ((prop.name == "Mesh" && Node::MESH == curNode->type) || - Node::ANIMMESH == curNode->type) { - /* This is the file name of the mesh - either - * animated or not. We need to make sure we setup - * the correct post-processing settings here. - */ - unsigned int pp = 0; - BatchLoader::PropertyMap map; - - /* If the mesh is a static one remove all animations from the impor data - */ - if (Node::ANIMMESH != curNode->type) { - pp |= aiProcess_RemoveComponent; - SetGenericProperty(map.ints, AI_CONFIG_PP_RVC_FLAGS, - aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS); - } - - /* TODO: maybe implement the protection against recursive - * loading calls directly in BatchLoader? The current - * implementation is not absolutely safe. A LWS and an IRR - * file referencing each other *could* cause the system to - * recurse forever. - */ - - const std::string extension = GetExtension(prop.value); - if ("irr" == extension) { - ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively"); - } else { - curNode->id = batch.AddLoadRequest(prop.value, pp, &map); - curNode->meshPath = prop.value; - } - } else if (inAnimator && prop.name == "Type") { - // type of the animator - if (prop.value == "rotation") { - curAnim->type = Animator::ROTATION; - } else if (prop.value == "flyCircle") { - curAnim->type = Animator::FLY_CIRCLE; - } else if (prop.value == "flyStraight") { - curAnim->type = Animator::FLY_CIRCLE; - } else if (prop.value == "followSpline") { - curAnim->type = Animator::FOLLOW_SPLINE; - } else { - ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value); - - curAnim->type = Animator::UNKNOWN; - } - } - } - } - //} else if (reader->getNodeType() == EXN_ELEMENT_END && !ASSIMP_stricmp(reader->getNodeName(), "attributes")) { - } else if (attrib.type() == pugi::node_null && !ASSIMP_stricmp(attrib.name(), "attributes")) { - break; - } - } - } - break; - - /*case EXN_ELEMENT_END: - - // If we reached the end of a node, we need to continue processing its parent - if (!ASSIMP_stricmp(reader->getNodeName(), "node")) { - if (!curNode) { - // currently is no node set. We need to go - // back in the node hierarchy - if (!curParent) { - curParent = root; - ASSIMP_LOG_ERROR("IRR: Too many closing elements"); - } else - curParent = curParent->parent; - } else - curNode = nullptr; - } - // clear all flags - else if (!ASSIMP_stricmp(reader->getNodeName(), "materials")) { - inMaterials = false; - } else if (!ASSIMP_stricmp(reader->getNodeName(), "animators")) { - inAnimator = false; - } - break;*/ - - default: - // GCC complains that not all enumeration values are handled - break; - } - //} - - // Now iterate through all cameras and compute their final (horizontal) FOV - for (aiCamera *cam : cameras) { - // screen aspect could be missing - if (cam->mAspect) { - cam->mHorizontalFOV *= cam->mAspect; - } else { - ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV"); - } - } - - batch.LoadAll(); - - // Allocate a temporary scene data structure - aiScene *tempScene = new aiScene(); - tempScene->mRootNode = new aiNode(); - tempScene->mRootNode->mName.Set(""); - - // Copy the cameras to the output array - if (!cameras.empty()) { - tempScene->mNumCameras = (unsigned int)cameras.size(); - tempScene->mCameras = new aiCamera *[tempScene->mNumCameras]; - ::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras); - } - - // Copy the light sources to the output array - if (!lights.empty()) { - tempScene->mNumLights = (unsigned int)lights.size(); - tempScene->mLights = new aiLight *[tempScene->mNumLights]; - ::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights); - } - - // temporary data - std::vector anims; - std::vector materials; - std::vector attach; - std::vector meshes; - - // try to guess how much storage we'll need - anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2)); - meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2)); - materials.reserve(guessedMatCnt + (guessedMatCnt >> 2)); - - // Now process our scene-graph recursively: generate final - // meshes and generate animation channels for all nodes. - unsigned int defMatIdx = UINT_MAX; - GenerateGraph(root, tempScene->mRootNode, tempScene, - batch, meshes, anims, attach, materials, defMatIdx); - - if (!anims.empty()) { - tempScene->mNumAnimations = 1; - tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations]; - aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation(); - - // *********************************************************** - // This is only the global animation channel of the scene. - // If there are animated models, they will have separate - // animation channels in the scene. To display IRR scenes - // correctly, users will need to combine the global anim - // channel with all the local animations they want to play - // *********************************************************** - an->mName.Set("Irr_GlobalAnimChannel"); - - // copy all node animation channels to the global channel - an->mNumChannels = (unsigned int)anims.size(); - an->mChannels = new aiNodeAnim *[an->mNumChannels]; - ::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels); - } - if (!meshes.empty()) { - // copy all meshes to the temporary scene - tempScene->mNumMeshes = (unsigned int)meshes.size(); - tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes]; - ::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *)); - } - - // Copy all materials to the output array - if (!materials.empty()) { - tempScene->mNumMaterials = (unsigned int)materials.size(); - tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials]; - ::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials); - } - - // Now merge all sub scenes and attach them to the correct - // attachment points in the scenegraph. - SceneCombiner::MergeScenes(&pScene, tempScene, attach, - AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? ( - AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : - 0)); - - // If we have no meshes | no materials now set the INCOMPLETE - // scene flag. This is necessary if we failed to load all - // models from external files - if (!pScene->mNumMeshes || !pScene->mNumMaterials) { - ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE"); - pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - } - - // Finished ... everything destructs automatically and all - // temporary scenes have already been deleted by MergeScenes() - delete root; + // Finished ... everything destructs automatically and all + // temporary scenes have already been deleted by MergeScenes() + delete root; } #endif // !! ASSIMP_BUILD_NO_IRR_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.h b/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.h index 7fa239395..2a8bfd562 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,7 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace Assimp { +namespace Assimp { // --------------------------------------------------------------------------- /** Irr importer class. @@ -71,13 +71,13 @@ public: /** Returns whether the class can handle the format of the given file. * See BaseImporter::CanRead() for details. */ - bool CanRead( const std::string& pFile, IOSystem* pIOHandler, - bool checkSig) const override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; protected: - const aiImporterDesc* GetInfo () const override; - void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; - void SetupProperties(const Importer* pImp) override; + const aiImporterDesc *GetInfo() const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + void SetupProperties(const Importer *pImp) override; private: /** Data structure for a scene-graph node animator @@ -85,27 +85,19 @@ private: struct Animator { // Type of the animator enum AT { - UNKNOWN = 0x0, - ROTATION = 0x1, - FLY_CIRCLE = 0x2, - FLY_STRAIGHT = 0x3, + UNKNOWN = 0x0, + ROTATION = 0x1, + FLY_CIRCLE = 0x2, + FLY_STRAIGHT = 0x3, FOLLOW_SPLINE = 0x4, - OTHER = 0x5 + OTHER = 0x5 } type; - explicit Animator(AT t = UNKNOWN) - : type (t) - , speed ( ai_real( 0.001 ) ) - , direction ( ai_real( 0.0 ), ai_real( 1.0 ), ai_real( 0.0 ) ) - , circleRadius ( ai_real( 1.0) ) - , tightness ( ai_real( 0.5 ) ) - , loop (true) - , timeForWay (100) - { + explicit Animator(AT t = UNKNOWN) : + type(t), speed(ai_real(0.001)), direction(ai_real(0.0), ai_real(1.0), ai_real(0.0)), circleRadius(ai_real(1.0)), tightness(ai_real(0.5)), loop(true), timeForWay(100) { } - // common parameters ai_real speed; aiVector3D direction; @@ -128,11 +120,9 @@ private: /** Data structure for a scene-graph node in an IRR file */ - struct Node - { + struct Node { // Type of the node - enum ET - { + enum ET { LIGHT, CUBE, MESH, @@ -144,21 +134,20 @@ private: ANIMMESH } type; - explicit Node(ET t) - : type (t) - , scaling (1.0,1.0,1.0) // assume uniform scaling by default - , parent() - , framesPerSecond (0.0) - , id() - , sphereRadius (1.0) - , spherePolyCountX (100) - , spherePolyCountY (100) - { + explicit Node(ET t) : + type(t), scaling(1.0, 1.0, 1.0) // assume uniform scaling by default + , + parent(), + framesPerSecond(0.0), + id(), + sphereRadius(1.0), + spherePolyCountX(100), + spherePolyCountY(100) { // Generate a default name for the node char buffer[128]; static int cnt; - ai_snprintf(buffer, 128, "IrrNode_%i",cnt++); + ai_snprintf(buffer, 128, "IrrNode_%i", cnt++); name = std::string(buffer); // reserve space for up to 5 materials @@ -175,10 +164,10 @@ private: std::string name; // List of all child nodes - std::vector children; + std::vector children; // Parent node - Node* parent; + Node *parent; // Animated meshes: frames per second // 0.f if not specified @@ -190,13 +179,13 @@ private: // Meshes: List of materials to be assigned // along with their corresponding material flags - std::vector< std::pair > materials; + std::vector> materials; // Spheres: radius of the sphere to be generates ai_real sphereRadius; // Spheres: Number of polygons in the x,y direction - unsigned int spherePolyCountX,spherePolyCountY; + unsigned int spherePolyCountX, spherePolyCountY; // List of all animators assigned to the node std::list animators; @@ -204,40 +193,54 @@ private: /** Data structure for a vertex in an IRR skybox */ - struct SkyboxVertex - { + struct SkyboxVertex { SkyboxVertex() = default; //! Construction from single vertex components SkyboxVertex(ai_real px, ai_real py, ai_real pz, - ai_real nx, ai_real ny, ai_real nz, - ai_real uvx, ai_real uvy) + ai_real nx, ai_real ny, ai_real nz, + ai_real uvx, ai_real uvy) - : position (px,py,pz) - , normal (nx,ny,nz) - , uv (uvx,uvy,0.0) - {} + : + position(px, py, pz), normal(nx, ny, nz), uv(uvx, uvy, 0.0) {} aiVector3D position, normal, uv; }; + // ------------------------------------------------------------------- + // Parse tag from XML file and extract child node + // @param node XML node + // @param guessedMeshesContained number of extra guessed meshes + IRRImporter::Node *ParseNode(pugi::xml_node &node, BatchLoader& batch); + + // ------------------------------------------------------------------- + // Parse tags within tags and apply to scene node + // @param attributeNode XML child node + // @param nd Attributed scene node + void ParseNodeAttributes(pugi::xml_node &attributeNode, IRRImporter::Node *nd, BatchLoader& batch); + + // ------------------------------------------------------------------- + // Parse an node and attach an animator to a node + // @param animatorNode XML animator node + // @param nd Animated scene node + void ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd); // ------------------------------------------------------------------- /// Fill the scene-graph recursively - void GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, - BatchLoader& batch, - std::vector& meshes, - std::vector& anims, - std::vector& attach, - std::vector& materials, - unsigned int& defaultMatIdx); + void GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene, + BatchLoader &batch, + std::vector &meshes, + std::vector &anims, + std::vector &attach, + std::vector &materials, + unsigned int &defaultMatIdx); // ------------------------------------------------------------------- /// Generate a mesh that consists of just a single quad - aiMesh* BuildSingleQuadMesh(const SkyboxVertex& v1, - const SkyboxVertex& v2, - const SkyboxVertex& v3, - const SkyboxVertex& v4); + aiMesh *BuildSingleQuadMesh(const SkyboxVertex &v1, + const SkyboxVertex &v2, + const SkyboxVertex &v3, + const SkyboxVertex &v4); // ------------------------------------------------------------------- /// Build a sky-box @@ -245,8 +248,8 @@ private: /// @param meshes Receives 6 output meshes /// @param materials The last 6 materials are assigned to the newly /// created meshes. The names of the materials are adjusted. - void BuildSkybox(std::vector& meshes, - std::vector materials); + void BuildSkybox(std::vector &meshes, + std::vector materials); // ------------------------------------------------------------------- /** Copy a material for a mesh to the output material list @@ -256,10 +259,10 @@ private: * @param defMatIdx Default material index - UINT_MAX if not present * @param mesh Mesh to work on */ - void CopyMaterial(std::vector& materials, - std::vector< std::pair >& inmaterials, - unsigned int& defMatIdx, - aiMesh* mesh); + void CopyMaterial(std::vector &materials, + std::vector> &inmaterials, + unsigned int &defMatIdx, + aiMesh *mesh); // ------------------------------------------------------------------- /** Compute animations for a specific node @@ -267,8 +270,8 @@ private: * @param root Node to be processed * @param anims The list of output animations */ - void ComputeAnimations(Node* root, aiNode* real, - std::vector& anims); + void ComputeAnimations(Node *root, aiNode *real, + std::vector &anims); private: /// Configuration option: desired output FPS @@ -276,6 +279,12 @@ private: /// Configuration option: speed flag was set? bool configSpeedFlag; + + std::vector cameras; + std::vector lights; + unsigned int guessedMeshCnt; + unsigned int guessedMatCnt; + unsigned int guessedAnimCnt; }; } // end of namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp b/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp index c219e26f2..4a2f70882 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,447 +56,466 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { - "Irrlicht Mesh Reader", - "", - "", - "http://irrlicht.sourceforge.net/", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "xml irrmesh" +static constexpr aiImporterDesc desc = { + "Irrlicht Mesh Reader", + "", + "", + "http://irrlicht.sourceforge.net/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "xml irrmesh" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -IRRMeshImporter::IRRMeshImporter() : - BaseImporter(), - IrrlichtBase() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -IRRMeshImporter::~IRRMeshImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { - /* NOTE: A simple check for the file extension is not enough - * here. Irrmesh and irr are easy, but xml is too generic - * and could be collada, too. So we need to open the file and - * search for typical tokens. - */ - static const char *tokens[] = { "irrmesh" }; - return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); + /* NOTE: A simple check for the file extension is not enough + * here. Irrmesh and irr are easy, but xml is too generic + * and could be collada, too. So we need to open the file and + * search for typical tokens. + */ + static const char *tokens[] = { "irrmesh" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); } // ------------------------------------------------------------------------------------------------ // Get a list of all file extensions which are handled by this class const aiImporterDesc *IRRMeshImporter::GetInfo() const { - return &desc; + return &desc; } static void releaseMaterial(aiMaterial **mat) { - if (*mat != nullptr) { - delete *mat; - *mat = nullptr; - } + if (*mat != nullptr) { + delete *mat; + *mat = nullptr; + } } static void releaseMesh(aiMesh **mesh) { - if (*mesh != nullptr) { - delete *mesh; - *mesh = nullptr; - } + if (*mesh != nullptr) { + delete *mesh; + *mesh = nullptr; + } } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void IRRMeshImporter::InternReadFile(const std::string &pFile, - aiScene *pScene, IOSystem *pIOHandler) { - std::unique_ptr file(pIOHandler->Open(pFile)); + aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr file(pIOHandler->Open(pFile)); - // Check whether we can read from the file - if (file.get() == NULL) + // Check whether we can read from the file + if (file == nullptr) { throw DeadlyImportError("Failed to open IRRMESH file ", pFile); + } - // Construct the irrXML parser - XmlParser parser; - if (!parser.parse( file.get() )) { + // Construct the irrXML parser + XmlParser parser; + if (!parser.parse(file.get())) { throw DeadlyImportError("XML parse error while loading IRRMESH file ", pFile); } XmlNode root = parser.getRootNode(); - // final data - std::vector materials; - std::vector meshes; - materials.reserve(5); - meshes.reserve(5); + // final data + std::vector materials; + std::vector meshes; + materials.reserve(5); + meshes.reserve(5); - // temporary data - current mesh buffer - aiMaterial *curMat = nullptr; - aiMesh *curMesh = nullptr; - unsigned int curMatFlags = 0; + // temporary data - current mesh buffer + // TODO move all these to inside loop + aiMaterial *curMat = nullptr; + aiMesh *curMesh = nullptr; + unsigned int curMatFlags = 0; - std::vector curVertices, curNormals, curTangents, curBitangents; - std::vector curColors; - std::vector curUVs, curUV2s; + std::vector curVertices, curNormals, curTangents, curBitangents; + std::vector curColors; + std::vector curUVs, curUV2s; - // some temporary variables - int textMeaning = 0; - int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents - bool useColors = false; + // some temporary variables + // textMeaning is a 15 year old variable, that could've been an enum + // int textMeaning = 0; // 0=none? 1=vertices 2=indices + // int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents + bool useColors = false; - // Parse the XML file - for (pugi::xml_node child : root.children()) { - if (child.type() == pugi::node_element) { - if (!ASSIMP_stricmp(child.name(), "buffer") && (curMat || curMesh)) { - // end of previous buffer. A material and a mesh should be there - if (!curMat || !curMesh) { - ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material"); - releaseMaterial(&curMat); - releaseMesh(&curMesh); - } else { - materials.push_back(curMat); - meshes.push_back(curMesh); - } - curMat = nullptr; - curMesh = nullptr; + // irrmesh files have a top level owning multiple nodes. + // Each contains , , and + // tags here directly owns the material data specs + // are a vertex per line, contains position, UV1 coords, maybe UV2, normal, tangent, bitangent + // is ignored, I think assimp recalculates those? - curVertices.clear(); - curColors.clear(); - curNormals.clear(); - curUV2s.clear(); - curUVs.clear(); - curTangents.clear(); - curBitangents.clear(); - } + // Parse the XML file + pugi::xml_node const &meshNode = root.child("mesh"); + for (pugi::xml_node bufferNode : meshNode.children()) { + if (ASSIMP_stricmp(bufferNode.name(), "buffer")) { + // Might be a useless warning + ASSIMP_LOG_WARN("IRRMESH: Ignoring non buffer node <", bufferNode.name(), "> in mesh declaration"); + continue; + } - if (!ASSIMP_stricmp(child.name(), "material")) { - if (curMat) { - ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please"); - releaseMaterial(&curMat); - } - curMat = ParseMaterial(curMatFlags); - } - /* no else here! */ if (!ASSIMP_stricmp(child.name(), "vertices")) { - pugi::xml_attribute attr = child.attribute("vertexCount"); - int num = attr.as_int(); - //int num = reader->getAttributeValueAsInt("vertexCount"); + curMat = nullptr; + curMesh = nullptr; - if (!num) { - // This is possible ... remove the mesh from the list and skip further reading - ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices"); + curVertices.clear(); + curColors.clear(); + curNormals.clear(); + curUV2s.clear(); + curUVs.clear(); + curTangents.clear(); + curBitangents.clear(); - releaseMaterial(&curMat); - releaseMesh(&curMesh); - textMeaning = 0; - continue; - } + // TODO ensure all three nodes are present and populated + // before allocating everything - curVertices.reserve(num); - curNormals.reserve(num); - curColors.reserve(num); - curUVs.reserve(num); + // Get first material node + pugi::xml_node materialNode = bufferNode.child("material"); + if (materialNode) { + curMat = ParseMaterial(materialNode, curMatFlags); + // Warn if there's more materials + if (materialNode.next_sibling("material")) { + ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please"); + } + } else { + ASSIMP_LOG_ERROR("IRRMESH: Buffer must contain one material"); + continue; + } - // Determine the file format - //const char *t = reader->getAttributeValueSafe("type"); - pugi::xml_attribute t = child.attribute("type"); - if (!ASSIMP_stricmp("2tcoords", t.name())) { - curUV2s.reserve(num); - vertexFormat = 1; + // Get first vertices node + pugi::xml_node verticesNode = bufferNode.child("vertices"); + if (verticesNode) { + pugi::xml_attribute vertexCountAttrib = verticesNode.attribute("vertexCount"); + int vertexCount = vertexCountAttrib.as_int(); + if (vertexCount == 0) { + // This is possible ... remove the mesh from the list and skip further reading + ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices"); + releaseMaterial(&curMat); + continue; // Bail out early + }; - if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) { - // ********************************************************* - // We have a second texture! So use this UV channel - // for it. The 2nd texture can be either a normal - // texture (solid_2layer or lightmap_xxx) or a normal - // map (normal_..., parallax_...) - // ********************************************************* - int idx = 1; - aiMaterial *mat = (aiMaterial *)curMat; + curVertices.reserve(vertexCount); + curNormals.reserve(vertexCount); + curColors.reserve(vertexCount); + curUVs.reserve(vertexCount); - if (curMatFlags & AI_IRRMESH_MAT_lightmap) { - mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0)); - } else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) { - mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); - } else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) { - mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1)); - } - } - } else if (!ASSIMP_stricmp("tangents", t.name())) { - curTangents.reserve(num); - curBitangents.reserve(num); - vertexFormat = 2; - } else if (ASSIMP_stricmp("standard", t.name())) { - releaseMaterial(&curMat); - ASSIMP_LOG_WARN("IRRMESH: Unknown vertex format"); - } else - vertexFormat = 0; - textMeaning = 1; - } else if (!ASSIMP_stricmp(child.name(), "indices")) { - if (curVertices.empty() && curMat) { - releaseMaterial(&curMat); - throw DeadlyImportError("IRRMESH: indices must come after vertices"); - } + VertexFormat vertexFormat; + // Determine the file format + pugi::xml_attribute typeAttrib = verticesNode.attribute("type"); + if (!ASSIMP_stricmp("2tcoords", typeAttrib.value())) { + curUV2s.reserve(vertexCount); + vertexFormat = VertexFormat::t2coord; + if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) { + // ********************************************************* + // We have a second texture! So use this UV channel + // for it. The 2nd texture can be either a normal + // texture (solid_2layer or lightmap_xxx) or a normal + // map (normal_..., parallax_...) + // ********************************************************* + int idx = 1; + aiMaterial *mat = (aiMaterial *)curMat; - textMeaning = 2; + if (curMatFlags & AI_IRRMESH_MAT_lightmap) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0)); + } else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0)); + } else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) { + mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1)); + } + } + } else if (!ASSIMP_stricmp("tangents", typeAttrib.value())) { + curTangents.reserve(vertexCount); + curBitangents.reserve(vertexCount); + vertexFormat = VertexFormat::tangent; + } else if (!ASSIMP_stricmp("standard", typeAttrib.value())) { + vertexFormat = VertexFormat::standard; + } else { + // Unsupported format, discard whole buffer/mesh + // Assuming we have a correct material, then release it + // We don't have a correct mesh for sure here + releaseMaterial(&curMat); + ASSIMP_LOG_ERROR("IRRMESH: Unknown vertex format"); + continue; // Skip rest of buffer + }; - // start a new mesh - curMesh = new aiMesh(); + // We know what format buffer is, collect numbers + std::string v = verticesNode.text().get(); + const char *end = v.c_str() + v.size(); + ParseBufferVertices(v.c_str(), end, vertexFormat, + curVertices, curNormals, + curTangents, curBitangents, + curUVs, curUV2s, curColors, useColors); + } - // allocate storage for all faces - pugi::xml_attribute attr = child.attribute("indexCount"); - curMesh->mNumVertices = attr.as_int(); - if (!curMesh->mNumVertices) { - // This is possible ... remove the mesh from the list and skip further reading - ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices"); + // Get indices + // At this point we have some vertices and a valid material + // Collect indices and create aiMesh at the same time + pugi::xml_node indicesNode = bufferNode.child("indices"); + if (indicesNode) { + // start a new mesh + curMesh = new aiMesh(); - // mesh - away - releaseMesh(&curMesh); + // allocate storage for all faces + pugi::xml_attribute attr = indicesNode.attribute("indexCount"); + curMesh->mNumVertices = attr.as_int(); + if (!curMesh->mNumVertices) { + // This is possible ... remove the mesh from the list and skip further reading + ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices"); - // material - away - releaseMaterial(&curMat); + // mesh - away + releaseMesh(&curMesh); - textMeaning = 0; - continue; - } + // material - away + releaseMaterial(&curMat); + continue; // Go to next buffer + } - if (curMesh->mNumVertices % 3) { - ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3"); - } + if (curMesh->mNumVertices % 3) { + ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3"); + } - curMesh->mNumFaces = curMesh->mNumVertices / 3; - curMesh->mFaces = new aiFace[curMesh->mNumFaces]; + curMesh->mNumFaces = curMesh->mNumVertices / 3; + curMesh->mFaces = new aiFace[curMesh->mNumFaces]; - // setup some members - curMesh->mMaterialIndex = (unsigned int)materials.size(); - curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + // setup some members + curMesh->mMaterialIndex = (unsigned int)materials.size(); + curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; - // allocate storage for all vertices - curMesh->mVertices = new aiVector3D[curMesh->mNumVertices]; + // allocate storage for all vertices + curMesh->mVertices = new aiVector3D[curMesh->mNumVertices]; - if (curNormals.size() == curVertices.size()) { - curMesh->mNormals = new aiVector3D[curMesh->mNumVertices]; - } - if (curTangents.size() == curVertices.size()) { - curMesh->mTangents = new aiVector3D[curMesh->mNumVertices]; - } - if (curBitangents.size() == curVertices.size()) { - curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices]; - } - if (curColors.size() == curVertices.size() && useColors) { - curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices]; - } - if (curUVs.size() == curVertices.size()) { - curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices]; - } - if (curUV2s.size() == curVertices.size()) { - curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices]; - } - } - //break; + if (curNormals.size() == curVertices.size()) { + curMesh->mNormals = new aiVector3D[curMesh->mNumVertices]; + } + if (curTangents.size() == curVertices.size()) { + curMesh->mTangents = new aiVector3D[curMesh->mNumVertices]; + } + if (curBitangents.size() == curVertices.size()) { + curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices]; + } + if (curColors.size() == curVertices.size() && useColors) { + curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices]; + } + if (curUVs.size() == curVertices.size()) { + curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices]; + } + if (curUV2s.size() == curVertices.size()) { + curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices]; + } - //case EXN_TEXT: { - const char *sz = child.child_value(); - if (textMeaning == 1) { - textMeaning = 0; + // read indices + aiFace *curFace = curMesh->mFaces; + aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces; - // read vertices - do { - SkipSpacesAndLineEnd(&sz); - aiVector3D temp; - aiColor4D c; + aiVector3D *pcV = curMesh->mVertices; + aiVector3D *pcN = curMesh->mNormals; + aiVector3D *pcT = curMesh->mTangents; + aiVector3D *pcB = curMesh->mBitangents; + aiColor4D *pcC0 = curMesh->mColors[0]; + aiVector3D *pcT0 = curMesh->mTextureCoords[0]; + aiVector3D *pcT1 = curMesh->mTextureCoords[1]; - // Read the vertex position - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + unsigned int curIdx = 0; + unsigned int total = 0; - sz = fast_atoreal_move(sz, (float &)temp.y); - SkipSpaces(&sz); + // NOTE this might explode for UTF-16 and wchars + const char *sz = indicesNode.text().get(); + const char *end = sz + std::strlen(sz); + + // For each index loop over aiMesh faces + while (SkipSpacesAndLineEnd(&sz, end)) { + if (curFace >= faceEnd) { + ASSIMP_LOG_ERROR("IRRMESH: Too many indices"); + break; + } + // if new face + if (!curIdx) { + curFace->mNumIndices = 3; + curFace->mIndices = new unsigned int[3]; + } - sz = fast_atoreal_move(sz, (float &)temp.z); - SkipSpaces(&sz); - curVertices.push_back(temp); + // Read index base 10 + // function advances the pointer + unsigned int idx = strtoul10(sz, &sz); + if (idx >= curVertices.size()) { + ASSIMP_LOG_ERROR("IRRMESH: Index out of range"); + idx = 0; + } - // Read the vertex normals - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + // make up our own indices? + curFace->mIndices[curIdx] = total++; - sz = fast_atoreal_move(sz, (float &)temp.y); - SkipSpaces(&sz); + // Copy over data to aiMesh + *pcV++ = curVertices[idx]; + if (pcN) + *pcN++ = curNormals[idx]; + if (pcT) + *pcT++ = curTangents[idx]; + if (pcB) + *pcB++ = curBitangents[idx]; + if (pcC0) + *pcC0++ = curColors[idx]; + if (pcT0) + *pcT0++ = curUVs[idx]; + if (pcT1) + *pcT1++ = curUV2s[idx]; - sz = fast_atoreal_move(sz, (float &)temp.z); - SkipSpaces(&sz); - curNormals.push_back(temp); + // start new face + if (++curIdx == 3) { + ++curFace; + curIdx = 0; + } + } + // We should be at the end of mFaces + if (curFace != faceEnd) { + ASSIMP_LOG_ERROR("IRRMESH: Not enough indices"); + } + } - // read the vertex colors - uint32_t clr = strtoul16(sz, &sz); - ColorFromARGBPacked(clr, c); + // Finish processing the mesh - do some small material workarounds + if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) { + // Take the opacity value of the current material + // from the common vertex color alpha + aiMaterial *mat = (aiMaterial *)curMat; + mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY); + } + + // end of previous buffer. A material and a mesh should be there + if (!curMat || !curMesh) { + ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material"); + releaseMaterial(&curMat); + releaseMesh(&curMesh); + } else { + materials.push_back(curMat); + meshes.push_back(curMesh); + } + } - if (!curColors.empty() && c != *(curColors.end() - 1)) - useColors = true; + // If one is empty then so is the other + if (materials.empty() || meshes.empty()) { + throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file"); + } - curColors.push_back(c); - SkipSpaces(&sz); + // now generate the output scene + pScene->mNumMeshes = (unsigned int)meshes.size(); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mMeshes[i] = meshes[i]; - // read the first UV coordinate set - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + // clean this value ... + pScene->mMeshes[i]->mNumUVComponents[3] = 0; + } - sz = fast_atoreal_move(sz, (float &)temp.y); - SkipSpaces(&sz); - temp.z = 0.f; - temp.y = 1.f - temp.y; // DX to OGL - curUVs.push_back(temp); + pScene->mNumMaterials = (unsigned int)materials.size(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + ::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials); - // read the (optional) second UV coordinate set - if (vertexFormat == 1) { - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mName.Set(""); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; - sz = fast_atoreal_move(sz, (float &)temp.y); - temp.y = 1.f - temp.y; // DX to OGL - curUV2s.push_back(temp); - } - // read optional tangent and bitangent vectors - else if (vertexFormat == 2) { - // tangents - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mRootNode->mMeshes[i] = i; + }; +} - sz = fast_atoreal_move(sz, (float &)temp.z); - SkipSpaces(&sz); +void IRRMeshImporter::ParseBufferVertices(const char *sz, const char *end, VertexFormat vertexFormat, + std::vector &vertices, std::vector &normals, + std::vector &tangents, std::vector &bitangents, + std::vector &UVs, std::vector &UV2s, + std::vector &colors, bool &useColors) { + // read vertices + do { + SkipSpacesAndLineEnd(&sz, end); + aiVector3D temp; + aiColor4D c; - sz = fast_atoreal_move(sz, (float &)temp.y); - SkipSpaces(&sz); - temp.y *= -1.0f; - curTangents.push_back(temp); + // Read the vertex position + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - // bitangents - sz = fast_atoreal_move(sz, (float &)temp.x); - SkipSpaces(&sz); + sz = fast_atoreal_move(sz, (float &)temp.y); + SkipSpaces(&sz, end); - sz = fast_atoreal_move(sz, (float &)temp.z); - SkipSpaces(&sz); + sz = fast_atoreal_move(sz, (float &)temp.z); + SkipSpaces(&sz, end); + vertices.push_back(temp); - sz = fast_atoreal_move(sz, (float &)temp.y); - SkipSpaces(&sz); - temp.y *= -1.0f; - curBitangents.push_back(temp); - } - } + // Read the vertex normals + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - /* IMPORTANT: We assume that each vertex is specified in one - line. So we can skip the rest of the line - unknown vertex - elements are ignored. - */ + sz = fast_atoreal_move(sz, (float &)temp.y); + SkipSpaces(&sz, end); - while (SkipLine(&sz)); - } else if (textMeaning == 2) { - textMeaning = 0; + sz = fast_atoreal_move(sz, (float &)temp.z); + SkipSpaces(&sz, end); + normals.push_back(temp); - // read indices - aiFace *curFace = curMesh->mFaces; - aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces; + // read the vertex colors + uint32_t clr = strtoul16(sz, &sz); + ColorFromARGBPacked(clr, c); - aiVector3D *pcV = curMesh->mVertices; - aiVector3D *pcN = curMesh->mNormals; - aiVector3D *pcT = curMesh->mTangents; - aiVector3D *pcB = curMesh->mBitangents; - aiColor4D *pcC0 = curMesh->mColors[0]; - aiVector3D *pcT0 = curMesh->mTextureCoords[0]; - aiVector3D *pcT1 = curMesh->mTextureCoords[1]; + // If we're pushing more than one distinct color + if (!colors.empty() && c != *(colors.end() - 1)) + useColors = true; - unsigned int curIdx = 0; - unsigned int total = 0; - while (SkipSpacesAndLineEnd(&sz)) { - if (curFace >= faceEnd) { - ASSIMP_LOG_ERROR("IRRMESH: Too many indices"); - break; - } - if (!curIdx) { - curFace->mNumIndices = 3; - curFace->mIndices = new unsigned int[3]; - } + colors.push_back(c); + SkipSpaces(&sz, end); - unsigned int idx = strtoul10(sz, &sz); - if (idx >= curVertices.size()) { - ASSIMP_LOG_ERROR("IRRMESH: Index out of range"); - idx = 0; - } + // read the first UV coordinate set + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - curFace->mIndices[curIdx] = total++; + sz = fast_atoreal_move(sz, (float &)temp.y); + SkipSpaces(&sz, end); + temp.z = 0.f; + temp.y = 1.f - temp.y; // DX to OGL + UVs.push_back(temp); - *pcV++ = curVertices[idx]; - if (pcN) *pcN++ = curNormals[idx]; - if (pcT) *pcT++ = curTangents[idx]; - if (pcB) *pcB++ = curBitangents[idx]; - if (pcC0) *pcC0++ = curColors[idx]; - if (pcT0) *pcT0++ = curUVs[idx]; - if (pcT1) *pcT1++ = curUV2s[idx]; + // NOTE these correspond to specific S3DVertex* structs in irr sourcecode + // So by definition, all buffers have either UV2 or tangents or neither + // read the (optional) second UV coordinate set + if (vertexFormat == VertexFormat::t2coord) { + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - if (++curIdx == 3) { - ++curFace; - curIdx = 0; - } - } + sz = fast_atoreal_move(sz, (float &)temp.y); + temp.y = 1.f - temp.y; // DX to OGL + UV2s.push_back(temp); + } + // read optional tangent and bitangent vectors + else if (vertexFormat == VertexFormat::tangent) { + // tangents + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - if (curFace != faceEnd) - ASSIMP_LOG_ERROR("IRRMESH: Not enough indices"); + sz = fast_atoreal_move(sz, (float &)temp.z); + SkipSpaces(&sz, end); - // Finish processing the mesh - do some small material workarounds - if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) { - // Take the opacity value of the current material - // from the common vertex color alpha - aiMaterial *mat = (aiMaterial *)curMat; - mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY); - } - } - } - } + sz = fast_atoreal_move(sz, (float &)temp.y); + SkipSpaces(&sz, end); + temp.y *= -1.0f; + tangents.push_back(temp); - // End of the last buffer. A material and a mesh should be there - if (curMat || curMesh) { - if (!curMat || !curMesh) { - ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material"); - releaseMaterial(&curMat); - releaseMesh(&curMesh); - } else { - materials.push_back(curMat); - meshes.push_back(curMesh); - } - } + // bitangents + sz = fast_atoreal_move(sz, (float &)temp.x); + SkipSpaces(&sz, end); - if (materials.empty()) { - throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file"); - } + sz = fast_atoreal_move(sz, (float &)temp.z); + SkipSpaces(&sz, end); - // now generate the output scene - pScene->mNumMeshes = (unsigned int)meshes.size(); - pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; - for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { - pScene->mMeshes[i] = meshes[i]; - - // clean this value ... - pScene->mMeshes[i]->mNumUVComponents[3] = 0; - } - - pScene->mNumMaterials = (unsigned int)materials.size(); - pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; - ::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials); - - pScene->mRootNode = new aiNode(); - pScene->mRootNode->mName.Set(""); - pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; - pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; - - for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { - pScene->mRootNode->mMeshes[i] = i; - } + sz = fast_atoreal_move(sz, (float &)temp.y); + SkipSpaces(&sz, end); + temp.y *= -1.0f; + bitangents.push_back(temp); + } + } while (SkipLine(&sz, end)); + // IMPORTANT: We assume that each vertex is specified in one + // line. So we can skip the rest of the line - unknown vertex + // elements are ignored. } #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.h b/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.h index 79c1e486b..4ab3615ee 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRMeshLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,8 +62,11 @@ namespace Assimp { */ class IRRMeshImporter : public BaseImporter, public IrrlichtBase { public: - IRRMeshImporter(); - ~IRRMeshImporter() override; + /// @brief The class constructor. + IRRMeshImporter() = default; + + /// @brief The class destructor. + ~IRRMeshImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -85,6 +88,19 @@ protected: */ void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + +private: + enum class VertexFormat { + standard = 0, // "standard" - also noted as 'normal' format elsewhere + t2coord = 1, // "2tcoord" - standard + 2 UV maps + tangent = 2, // "tangents" - standard + tangents and bitangents + }; + + void ParseBufferVertices(const char *sz, const char *end, VertexFormat vertexFormat, + std::vector &vertices, std::vector &normals, + std::vector &tangents, std::vector &bitangents, + std::vector &UVs, std::vector &UV2s, + std::vector &colors, bool &useColors); }; } // end of namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.cpp b/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.cpp index 8763b63ae..20d56bb02 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.cpp +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,302 +43,304 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Shared utilities for the IRR and IRRMESH loaders */ -//This section should be excluded only if both the Irrlicht AND the Irrlicht Mesh importers were omitted. +// This section should be excluded only if both the Irrlicht AND the Irrlicht Mesh importers were omitted. #if !(defined(ASSIMP_BUILD_NO_IRR_IMPORTER) && defined(ASSIMP_BUILD_NO_IRRMESH_IMPORTER)) #include "IRRShared.h" #include #include -#include #include +#include using namespace Assimp; // Transformation matrix to convert from Assimp to IRR space -const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4 ( - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); +const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); // ------------------------------------------------------------------------------------------------ // read a property in hexadecimal format (i.e. ffffffff) -void IrrlichtBase::ReadHexProperty(HexProperty &out ) { - for (pugi::xml_attribute attrib : mNode->attributes()) { +void IrrlichtBase::ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode) { + for (pugi::xml_attribute attrib : hexnode.attributes()) { if (!ASSIMP_stricmp(attrib.name(), "name")) { - out.name = std::string( attrib.value() ); - } else if (!ASSIMP_stricmp(attrib.name(),"value")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // parse the hexadecimal value - out.value = strtoul16(attrib.name()); + out.value = strtoul16(attrib.value()); } } } // ------------------------------------------------------------------------------------------------ // read a decimal property -void IrrlichtBase::ReadIntProperty(IntProperty & out) { - for (pugi::xml_attribute attrib : mNode->attributes()) { - if (!ASSIMP_stricmp(attrib.name(), "name")) { - out.name = std::string(attrib.value()); - } else if (!ASSIMP_stricmp(attrib.value(),"value")) { +void IrrlichtBase::ReadIntProperty(IntProperty &out, pugi::xml_node& intnode) { + for (pugi::xml_attribute attrib : intnode.attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // parse the int value - out.value = strtol10(attrib.name()); + out.value = strtol10(attrib.value()); } } } // ------------------------------------------------------------------------------------------------ // read a string property -void IrrlichtBase::ReadStringProperty( StringProperty& out) { - for (pugi::xml_attribute attrib : mNode->attributes()) { - if (!ASSIMP_stricmp(attrib.name(), "name")) { - out.name = std::string(attrib.value()); - } else if (!ASSIMP_stricmp(attrib.name(), "value")) { +void IrrlichtBase::ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode) { + for (pugi::xml_attribute attrib : stringnode.attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // simple copy the string - out.value = std::string(attrib.value()); + out.value = std::string(attrib.value()); } } } // ------------------------------------------------------------------------------------------------ // read a boolean property -void IrrlichtBase::ReadBoolProperty(BoolProperty &out) { - for (pugi::xml_attribute attrib : mNode->attributes()) { - if (!ASSIMP_stricmp(attrib.name(), "name")){ - out.name = std::string(attrib.value()); - } else if (!ASSIMP_stricmp(attrib.name(), "value")) { +void IrrlichtBase::ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode) { + for (pugi::xml_attribute attrib : boolnode.attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // true or false, case insensitive - out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true); + out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true); } } } // ------------------------------------------------------------------------------------------------ // read a float property -void IrrlichtBase::ReadFloatProperty(FloatProperty &out) { - for (pugi::xml_attribute attrib : mNode->attributes()) { - if (!ASSIMP_stricmp(attrib.name(), "name")) { - out.name = std::string(attrib.value()); - } else if (!ASSIMP_stricmp(attrib.name(), "value")) { +void IrrlichtBase::ReadFloatProperty(FloatProperty &out, pugi::xml_node &floatnode) { + for (pugi::xml_attribute attrib : floatnode.attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // just parse the float - out.value = fast_atof(attrib.value()); + out.value = fast_atof(attrib.value()); } } } // ------------------------------------------------------------------------------------------------ // read a vector property -void IrrlichtBase::ReadVectorProperty( VectorProperty &out ) { - for (pugi::xml_attribute attrib : mNode->attributes()) { - if (!ASSIMP_stricmp(attrib.name(), "name")) { - out.name = std::string(attrib.value()); - } else if (!ASSIMP_stricmp(attrib.name(), "value")) { +void IrrlichtBase::ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode) { + for (pugi::xml_attribute attrib : vectornode.attributes()) { + if (!ASSIMP_stricmp(attrib.name(), "name")) { + out.name = std::string(attrib.value()); + } else if (!ASSIMP_stricmp(attrib.name(), "value")) { // three floats, separated with commas const char *ptr = attrib.value(); + size_t len = std::strlen(ptr); + const char *end = ptr + len; - SkipSpaces(&ptr); - ptr = fast_atoreal_move( ptr,(float&)out.value.x ); - SkipSpaces(&ptr); + SkipSpaces(&ptr, end); + ptr = fast_atoreal_move(ptr, (float &)out.value.x); + SkipSpaces(&ptr, end); if (',' != *ptr) { ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition"); - } else { - SkipSpaces(ptr + 1, &ptr); - } - ptr = fast_atoreal_move( ptr,(float&)out.value.y ); - SkipSpaces(&ptr); + } else { + SkipSpaces(ptr + 1, &ptr, end); + } + ptr = fast_atoreal_move(ptr, (float &)out.value.y); + SkipSpaces(&ptr, end); if (',' != *ptr) { ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition"); - } else { - SkipSpaces(ptr + 1, &ptr); - } - ptr = fast_atoreal_move( ptr,(float&)out.value.z ); + } else { + SkipSpaces(ptr + 1, &ptr, end); + } + ptr = fast_atoreal_move(ptr, (float &)out.value.z); } } } // ------------------------------------------------------------------------------------------------ // Convert a string to a proper aiMappingMode -int ConvertMappingMode(const std::string& mode) { +int ConvertMappingMode(const std::string &mode) { if (mode == "texture_clamp_repeat") { return aiTextureMapMode_Wrap; - } else if (mode == "texture_clamp_mirror") { - return aiTextureMapMode_Mirror; - } + } else if (mode == "texture_clamp_mirror") { + return aiTextureMapMode_Mirror; + } return aiTextureMapMode_Clamp; } // ------------------------------------------------------------------------------------------------ // Parse a material from the XML file -aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) { - aiMaterial* mat = new aiMaterial(); +aiMaterial *IrrlichtBase::ParseMaterial(pugi::xml_node& materialNode, unsigned int &matFlags) { + aiMaterial *mat = new aiMaterial(); aiColor4D clr; aiString s; matFlags = 0; // zero output flags - int cnt = 0; // number of used texture channels + int cnt = 0; // number of used texture channels unsigned int nd = 0; - for (pugi::xml_node child : mNode->children()) { - if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties - HexProperty prop; - ReadHexProperty(prop); - if (prop.name == "Diffuse") { - ColorFromARGBPacked(prop.value, clr); - mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); - } else if (prop.name == "Ambient") { - ColorFromARGBPacked(prop.value, clr); - mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT); - } else if (prop.name == "Specular") { - ColorFromARGBPacked(prop.value, clr); - mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); - } + for (pugi::xml_node child : materialNode.children()) { + if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties + HexProperty prop; + ReadHexProperty(prop, child); + if (prop.name == "Diffuse") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (prop.name == "Ambient") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT); + } else if (prop.name == "Specular") { + ColorFromARGBPacked(prop.value, clr); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + } - // NOTE: The 'emissive' property causes problems. It is - // often != 0, even if there is obviously no light - // emitted by the described surface. In fact I think - // IRRLICHT ignores this property, too. + // NOTE: The 'emissive' property causes problems. It is + // often != 0, even if there is obviously no light + // emitted by the described surface. In fact I think + // IRRLICHT ignores this property, too. #if 0 else if (prop.name == "Emissive") { ColorFromARGBPacked(prop.value,clr); mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE); } #endif - } else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties - FloatProperty prop; - ReadFloatProperty(prop); - if (prop.name == "Shininess") { - mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS); - } - } else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties - BoolProperty prop; - ReadBoolProperty(prop); - if (prop.name == "Wireframe") { - int val = (prop.value ? true : false); - mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME); - } else if (prop.name == "GouraudShading") { - int val = (prop.value ? aiShadingMode_Gouraud : aiShadingMode_NoShading); - mat->AddProperty(&val, 1, AI_MATKEY_SHADING_MODEL); - } else if (prop.name == "BackfaceCulling") { - int val = (!prop.value); - mat->AddProperty(&val, 1, AI_MATKEY_TWOSIDED); - } - } else if (!ASSIMP_stricmp(child.name(), "texture") || - !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties - StringProperty prop; - ReadStringProperty(prop); - if (prop.value.length()) { - // material type (shader) - if (prop.name == "Type") { - if (prop.value == "solid") { - // default material ... - } else if (prop.value == "trans_vertex_alpha") { - matFlags = AI_IRRMESH_MAT_trans_vertex_alpha; - } else if (prop.value == "lightmap") { - matFlags = AI_IRRMESH_MAT_lightmap; - } else if (prop.value == "solid_2layer") { - matFlags = AI_IRRMESH_MAT_solid_2layer; - } else if (prop.value == "lightmap_m2") { - matFlags = AI_IRRMESH_MAT_lightmap_m2; - } else if (prop.value == "lightmap_m4") { - matFlags = AI_IRRMESH_MAT_lightmap_m4; - } else if (prop.value == "lightmap_light") { - matFlags = AI_IRRMESH_MAT_lightmap_light; - } else if (prop.value == "lightmap_light_m2") { - matFlags = AI_IRRMESH_MAT_lightmap_light_m2; - } else if (prop.value == "lightmap_light_m4") { - matFlags = AI_IRRMESH_MAT_lightmap_light_m4; - } else if (prop.value == "lightmap_add") { - matFlags = AI_IRRMESH_MAT_lightmap_add; - } else if (prop.value == "normalmap_solid" || - prop.value == "parallaxmap_solid") { // Normal and parallax maps are treated equally - matFlags = AI_IRRMESH_MAT_normalmap_solid; - } else if (prop.value == "normalmap_trans_vertex_alpha" || - prop.value == "parallaxmap_trans_vertex_alpha") { - matFlags = AI_IRRMESH_MAT_normalmap_tva; - } else if (prop.value == "normalmap_trans_add" || - prop.value == "parallaxmap_trans_add") { - matFlags = AI_IRRMESH_MAT_normalmap_ta; - } else { - ASSIMP_LOG_WARN("IRRMat: Unrecognized material type: ", prop.value); - } - } + } else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties + FloatProperty prop; + ReadFloatProperty(prop, child); + if (prop.name == "Shininess") { + mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS); + } + } else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties + BoolProperty prop; + ReadBoolProperty(prop, child); + if (prop.name == "Wireframe") { + int val = (prop.value ? true : false); + mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME); + } else if (prop.name == "GouraudShading") { + int val = (prop.value ? aiShadingMode_Gouraud : aiShadingMode_NoShading); + mat->AddProperty(&val, 1, AI_MATKEY_SHADING_MODEL); + } else if (prop.name == "BackfaceCulling") { + int val = (!prop.value); + mat->AddProperty(&val, 1, AI_MATKEY_TWOSIDED); + } + } else if (!ASSIMP_stricmp(child.name(), "texture") || + !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties + StringProperty prop; + ReadStringProperty(prop, child); + if (prop.value.length()) { + // material type (shader) + if (prop.name == "Type") { + if (prop.value == "solid") { + // default material ... + } else if (prop.value == "trans_vertex_alpha") { + matFlags = AI_IRRMESH_MAT_trans_vertex_alpha; + } else if (prop.value == "lightmap") { + matFlags = AI_IRRMESH_MAT_lightmap; + } else if (prop.value == "solid_2layer") { + matFlags = AI_IRRMESH_MAT_solid_2layer; + } else if (prop.value == "lightmap_m2") { + matFlags = AI_IRRMESH_MAT_lightmap_m2; + } else if (prop.value == "lightmap_m4") { + matFlags = AI_IRRMESH_MAT_lightmap_m4; + } else if (prop.value == "lightmap_light") { + matFlags = AI_IRRMESH_MAT_lightmap_light; + } else if (prop.value == "lightmap_light_m2") { + matFlags = AI_IRRMESH_MAT_lightmap_light_m2; + } else if (prop.value == "lightmap_light_m4") { + matFlags = AI_IRRMESH_MAT_lightmap_light_m4; + } else if (prop.value == "lightmap_add") { + matFlags = AI_IRRMESH_MAT_lightmap_add; + } else if (prop.value == "normalmap_solid" || + prop.value == "parallaxmap_solid") { // Normal and parallax maps are treated equally + matFlags = AI_IRRMESH_MAT_normalmap_solid; + } else if (prop.value == "normalmap_trans_vertex_alpha" || + prop.value == "parallaxmap_trans_vertex_alpha") { + matFlags = AI_IRRMESH_MAT_normalmap_tva; + } else if (prop.value == "normalmap_trans_add" || + prop.value == "parallaxmap_trans_add") { + matFlags = AI_IRRMESH_MAT_normalmap_ta; + } else { + ASSIMP_LOG_WARN("IRRMat: Unrecognized material type: ", prop.value); + } + } - // Up to 4 texture channels are supported - if (prop.name == "Texture1") { - // Always accept the primary texture channel - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); - } else if (prop.name == "Texture2" && cnt == 1) { - // 2-layer material lightmapped? - if (matFlags & AI_IRRMESH_MAT_lightmap) { - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_LIGHTMAP(0)); + // Up to 4 texture channels are supported + if (prop.name == "Texture1") { + // Always accept the primary texture channel + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } else if (prop.name == "Texture2" && cnt == 1) { + // 2-layer material lightmapped? + if (matFlags & AI_IRRMESH_MAT_lightmap) { + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_LIGHTMAP(0)); - // set the corresponding material flag - matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; - } else if (matFlags & AI_IRRMESH_MAT_normalmap_solid) { // alternatively: normal or parallax mapping - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_NORMALS(0)); + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else if (matFlags & AI_IRRMESH_MAT_normalmap_solid) { // alternatively: normal or parallax mapping + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_NORMALS(0)); - // set the corresponding material flag - matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; - } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { // or just as second diffuse texture - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(1)); - ++nd; + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { // or just as second diffuse texture + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(1)); + ++nd; - // set the corresponding material flag - matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; - } else { - ASSIMP_LOG_WARN("IRRmat: Skipping second texture"); - } - } else if (prop.name == "Texture3" && cnt == 2) { - // Irrlicht does not seem to use these channels. - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 1)); - } else if (prop.name == "Texture4" && cnt == 3) { - // Irrlicht does not seem to use these channels. - ++cnt; - s.Set(prop.value); - mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 2)); - } + // set the corresponding material flag + matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE; + } else { + ASSIMP_LOG_WARN("IRRmat: Skipping second texture"); + } + } else if (prop.name == "Texture3" && cnt == 2) { + // Irrlicht does not seem to use these channels. + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 1)); + } else if (prop.name == "Texture4" && cnt == 3) { + // Irrlicht does not seem to use these channels. + ++cnt; + s.Set(prop.value); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 2)); + } - // Texture mapping options - if (prop.name == "TextureWrap1" && cnt >= 1) { - int map = ConvertMappingMode(prop.value); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); - } else if (prop.name == "TextureWrap2" && cnt >= 2) { - int map = ConvertMappingMode(prop.value); - if (matFlags & AI_IRRMESH_MAT_lightmap) { - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_LIGHTMAP(0)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_LIGHTMAP(0)); - } else if (matFlags & (AI_IRRMESH_MAT_normalmap_solid)) { - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_NORMALS(0)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_NORMALS(0)); - } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(1)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(1)); - } - } else if (prop.name == "TextureWrap3" && cnt >= 3) { - int map = ConvertMappingMode(prop.value); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 1)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 1)); - } else if (prop.name == "TextureWrap4" && cnt >= 4) { - int map = ConvertMappingMode(prop.value); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 2)); - mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 2)); - } - } - } - //break; - /*case EXN_ELEMENT_END: + // Texture mapping options + if (prop.name == "TextureWrap1" && cnt >= 1) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + } else if (prop.name == "TextureWrap2" && cnt >= 2) { + int map = ConvertMappingMode(prop.value); + if (matFlags & AI_IRRMESH_MAT_lightmap) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_LIGHTMAP(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_LIGHTMAP(0)); + } else if (matFlags & (AI_IRRMESH_MAT_normalmap_solid)) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_NORMALS(0)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_NORMALS(0)); + } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(1)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(1)); + } + } else if (prop.name == "TextureWrap3" && cnt >= 3) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 1)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 1)); + } else if (prop.name == "TextureWrap4" && cnt >= 4) { + int map = ConvertMappingMode(prop.value); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 2)); + mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 2)); + } + } + } + // break; + /*case EXN_ELEMENT_END: // Assume there are no further nested nodes in elements if ( !ASSIMP_stricmp(reader->getNodeName(),"material") || @@ -378,8 +380,8 @@ aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) { break; } }*/ - } - ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete"); + } + //ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete"); return mat; } diff --git a/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.h b/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.h index 90e212d65..c04d20e52 100644 --- a/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.h +++ b/Engine/lib/assimp/code/AssetLib/Irr/IRRShared.h @@ -1,8 +1,8 @@ /** @file IRRShared.h - * @brief Shared utilities for the IRR and IRRMESH loaders - */ + * @brief Shared utilities for the IRR and IRRMESH loaders + */ #ifndef INCLUDED_AI_IRRSHARED_H #define INCLUDED_AI_IRRSHARED_H @@ -58,14 +58,11 @@ extern const aiMatrix4x4 AI_TO_IRR_MATRIX; */ class IrrlichtBase { protected: - IrrlichtBase() : - mNode(nullptr) { + IrrlichtBase() { // empty } - ~IrrlichtBase() { - // empty - } + ~IrrlichtBase() = default; /** @brief Data structure for a simple name-value property */ @@ -84,25 +81,25 @@ protected: /// XML reader instance XmlParser mParser; - pugi::xml_node *mNode; // ------------------------------------------------------------------- /** Parse a material description from the XML * @return The created material * @param matFlags Receives AI_IRRMESH_MAT_XX flags */ - aiMaterial *ParseMaterial(unsigned int &matFlags); + aiMaterial *ParseMaterial(pugi::xml_node &materialNode, unsigned int &matFlags); // ------------------------------------------------------------------- /** Read a property of the specified type from the current XML element. * @param out Receives output data + * @param node XML attribute element containing data */ - void ReadHexProperty(HexProperty &out); - void ReadStringProperty(StringProperty &out); - void ReadBoolProperty(BoolProperty &out); - void ReadFloatProperty(FloatProperty &out); - void ReadVectorProperty(VectorProperty &out); - void ReadIntProperty(IntProperty &out); + void ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode); + void ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode); + void ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode); + void ReadFloatProperty(FloatProperty &out, pugi::xml_node& floatnode); + void ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode); + void ReadIntProperty(IntProperty &out, pugi::xml_node& intnode); }; // ------------------------------------------------------------------------------------------------ diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.cpp b/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.cpp index c2ee2d9c0..5b9c6882e 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.cpp +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -162,8 +162,11 @@ void AnimResolver::UpdateAnimRangeSetup() { const double my_last = (*it).keys.back().time; const double delta = my_last - my_first; - const size_t old_size = (*it).keys.size(); + if (delta == 0.0) { + continue; + } + const size_t old_size = (*it).keys.size(); const float value_delta = (*it).keys.back().value - (*it).keys.front().value; // NOTE: We won't handle reset, linear and constant here. @@ -176,8 +179,7 @@ void AnimResolver::UpdateAnimRangeSetup() { case LWO::PrePostBehaviour_Oscillate: { const double start_time = delta - std::fmod(my_first - first, delta); std::vector::iterator n = std::find_if((*it).keys.begin(), (*it).keys.end(), - [start_time](double t) { return start_time > t; }), - m; + [start_time](double t) { return start_time > t; }), m; size_t ofs = 0; if (n != (*it).keys.end()) { @@ -463,7 +465,7 @@ void AnimResolver::GetKeys(std::vector &out, cur_z = envl_z->keys.begin(); end_x = end_y = end_z = false; - while (1) { + while (true) { aiVectorKey fill; diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.h b/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.h index 1e419d461..9daa7009c 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.h +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOAnimation.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOBLoader.cpp b/Engine/lib/assimp/code/AssetLib/LWO/LWOBLoader.cpp index a61e49a7f..b5c14f158 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOBLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOBLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -51,65 +51,56 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "LWOLoader.h" using namespace Assimp; - // ------------------------------------------------------------------------------------------------ -void LWOImporter::LoadLWOBFile() -{ +void LWOImporter::LoadLWOBFile() { LE_NCONST uint8_t* const end = mFileBuffer + fileSize; bool running = true; - while (running) - { - if (mFileBuffer + sizeof(IFF::ChunkHeader) > end)break; + while (running) { + if (mFileBuffer + sizeof(IFF::ChunkHeader) > end) + break; const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); - if (mFileBuffer + head.length > end) - { + if (mFileBuffer + head.length > end) { throw DeadlyImportError("LWOB: Invalid chunk length"); - break; } uint8_t* const next = mFileBuffer+head.length; - switch (head.type) - { + switch (head.type) { // vertex list - case AI_LWO_PNTS: - { + case AI_LWO_PNTS: { if (!mCurLayer->mTempPoints.empty()) ASSIMP_LOG_WARN("LWO: PNTS chunk encountered twice"); - else LoadLWOPoints(head.length); - break; - } - // face list - case AI_LWO_POLS: - { + else + LoadLWOPoints(head.length); + } break; + case AI_LWO_POLS: { // face list + if (!mCurLayer->mFaces.empty()) + ASSIMP_LOG_WARN("LWO: POLS chunk encountered twice"); + else + LoadLWOBPolygons(head.length); + } break; + + case AI_LWO_SRFS: // list of tags + { + if (!mTags->empty()) + ASSIMP_LOG_WARN("LWO: SRFS chunk encountered twice"); + else + LoadLWOTags(head.length); + } break; - if (!mCurLayer->mFaces.empty()) - ASSIMP_LOG_WARN("LWO: POLS chunk encountered twice"); - else LoadLWOBPolygons(head.length); - break; - } - // list of tags - case AI_LWO_SRFS: - { - if (!mTags->empty()) - ASSIMP_LOG_WARN("LWO: SRFS chunk encountered twice"); - else LoadLWOTags(head.length); - break; - } + case AI_LWO_SURF: // surface chunk + { + LoadLWOBSurface(head.length); + } break; - // surface chunk - case AI_LWO_SURF: - { - LoadLWOBSurface(head.length); + default: break; - } } mFileBuffer = next; } } // ------------------------------------------------------------------------------------------------ -void LWOImporter::LoadLWOBPolygons(unsigned int length) -{ +void LWOImporter::LoadLWOBPolygons(unsigned int length) { // first find out how many faces and vertices we'll finally need LE_NCONST uint16_t* const end = (LE_NCONST uint16_t*)(mFileBuffer+length); LE_NCONST uint16_t* cursor = (LE_NCONST uint16_t*)mFileBuffer; @@ -124,8 +115,7 @@ void LWOImporter::LoadLWOBPolygons(unsigned int length) CountVertsAndFacesLWOB(iNumVertices,iNumFaces,cursor,end); // allocate the output array and copy face indices - if (iNumFaces) - { + if (iNumFaces) { cursor = (LE_NCONST uint16_t*)mFileBuffer; mCurLayer->mFaces.resize(iNumFaces); @@ -136,10 +126,8 @@ void LWOImporter::LoadLWOBPolygons(unsigned int length) // ------------------------------------------------------------------------------------------------ void LWOImporter::CountVertsAndFacesLWOB(unsigned int& verts, unsigned int& faces, - LE_NCONST uint16_t*& cursor, const uint16_t* const end, unsigned int max) -{ - while (cursor < end && max--) - { + LE_NCONST uint16_t*& cursor, const uint16_t* const end, unsigned int max) { + while (cursor < end && max--) { uint16_t numIndices; // must have 2 shorts left for numIndices and surface if (end - cursor < 2) { @@ -155,8 +143,7 @@ void LWOImporter::CountVertsAndFacesLWOB(unsigned int& verts, unsigned int& face cursor += numIndices; int16_t surface; ::memcpy(&surface, cursor++, 2); - if (surface < 0) - { + if (surface < 0) { // there are detail polygons ::memcpy(&numIndices, cursor++, 2); CountVertsAndFacesLWOB(verts,faces,cursor,end,numIndices); @@ -165,21 +152,17 @@ void LWOImporter::CountVertsAndFacesLWOB(unsigned int& verts, unsigned int& face } // ------------------------------------------------------------------------------------------------ -void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, +void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator &it, LE_NCONST uint16_t*& cursor, const uint16_t* const end, - unsigned int max) -{ - while (cursor < end && max--) - { + unsigned int max) { + while (cursor < end && max--) { LWO::Face& face = *it;++it; uint16_t numIndices; ::memcpy(&numIndices, cursor++, 2); face.mNumIndices = numIndices; - if(face.mNumIndices) - { - if (cursor + face.mNumIndices >= end) - { + if(face.mNumIndices) { + if (cursor + face.mNumIndices >= end) { break; } face.mIndices = new unsigned int[face.mNumIndices]; @@ -188,8 +171,7 @@ void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, uint16_t index; ::memcpy(&index, cursor++, 2); mi = index; - if (mi > mCurLayer->mTempPoints.size()) - { + if (mi > mCurLayer->mTempPoints.size()) { ASSIMP_LOG_WARN("LWOB: face index is out of range"); mi = (unsigned int)mCurLayer->mTempPoints.size()-1; } @@ -199,15 +181,13 @@ void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, } int16_t surface; ::memcpy(&surface, cursor++, 2); - if (surface < 0) - { + if (surface < 0) { surface = -surface; // there are detail polygons. uint16_t numPolygons; ::memcpy(&numPolygons, cursor++, 2); - if (cursor < end) - { + if (cursor < end) { CopyFaceIndicesLWOB(it,cursor,end,numPolygons); } } @@ -216,8 +196,7 @@ void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, } // ------------------------------------------------------------------------------------------------ -LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned int size) -{ +LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned int size) { list.emplace_back(); LWO::Texture* tex = &list.back(); @@ -225,8 +204,7 @@ LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned i GetS0(type,size); const char* s = type.c_str(); - if(strstr(s, "Image Map")) - { + if(strstr(s, "Image Map")) { // Determine mapping type if(strstr(s, "Planar")) tex->mapMode = LWO::Texture::Planar; @@ -238,9 +216,7 @@ LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned i tex->mapMode = LWO::Texture::Cubic; else if(strstr(s, "Front")) tex->mapMode = LWO::Texture::FrontProjection; - } - else - { + } else { // procedural or gradient, not supported ASSIMP_LOG_ERROR("LWOB: Unsupported legacy texture: ", type); } @@ -249,8 +225,7 @@ LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned i } // ------------------------------------------------------------------------------------------------ -void LWOImporter::LoadLWOBSurface(unsigned int size) -{ +void LWOImporter::LoadLWOBSurface(unsigned int size) { LE_NCONST uint8_t* const end = mFileBuffer + size; mSurfaces->push_back( LWO::Surface () ); @@ -278,148 +253,147 @@ void LWOImporter::LoadLWOBSurface(unsigned int size) } uint8_t* const next = mFileBuffer+head.length; - switch (head.type) - { - // diffuse color - case AI_LWO_COLR: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,COLR,3); - surf.mColor.r = GetU1() / 255.0f; - surf.mColor.g = GetU1() / 255.0f; - surf.mColor.b = GetU1() / 255.0f; - break; - } - // diffuse strength ... - case AI_LWO_DIFF: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,DIFF,2); - surf.mDiffuseValue = GetU2() / 255.0f; - break; - } - // specular strength ... - case AI_LWO_SPEC: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SPEC,2); - surf.mSpecularValue = GetU2() / 255.0f; - break; - } - // luminosity ... - case AI_LWO_LUMI: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,LUMI,2); - surf.mLuminosity = GetU2() / 255.0f; - break; - } - // transparency - case AI_LWO_TRAN: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TRAN,2); - surf.mTransparency = GetU2() / 255.0f; - break; - } - // surface flags - case AI_LWO_FLAG: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,FLAG,2); - uint16_t flag = GetU2(); - if (flag & 0x4 ) surf.mMaximumSmoothAngle = 1.56207f; - if (flag & 0x8 ) surf.mColorHighlights = 1.f; - if (flag & 0x100) surf.bDoubleSided = true; - break; - } - // maximum smoothing angle - case AI_LWO_SMAN: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SMAN,4); - surf.mMaximumSmoothAngle = std::fabs( GetF4() ); - break; - } - // glossiness - case AI_LWO_GLOS: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,GLOS,2); - surf.mGlossiness = (float)GetU2(); - break; - } - // color texture - case AI_LWO_CTEX: - { - pTex = SetupNewTextureLWOB(surf.mColorTextures, - head.length); - break; - } - // diffuse texture - case AI_LWO_DTEX: - { - pTex = SetupNewTextureLWOB(surf.mDiffuseTextures, - head.length); - break; - } - // specular texture - case AI_LWO_STEX: - { - pTex = SetupNewTextureLWOB(surf.mSpecularTextures, - head.length); - break; - } - // bump texture - case AI_LWO_BTEX: - { - pTex = SetupNewTextureLWOB(surf.mBumpTextures, - head.length); - break; - } - // transparency texture - case AI_LWO_TTEX: - { - pTex = SetupNewTextureLWOB(surf.mOpacityTextures, - head.length); - break; - } - // texture path - case AI_LWO_TIMG: - { - if (pTex) { - GetS0(pTex->mFileName,head.length); - } else { - ASSIMP_LOG_WARN("LWOB: Unexpected TIMG chunk"); + switch (head.type) { + // diffuse color + case AI_LWO_COLR: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,COLR,3); + surf.mColor.r = GetU1() / 255.0f; + surf.mColor.g = GetU1() / 255.0f; + surf.mColor.b = GetU1() / 255.0f; + break; } - break; - } - // texture strength - case AI_LWO_TVAL: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TVAL,1); - if (pTex) { - pTex->mStrength = (float)GetU1()/ 255.f; - } else { - ASSIMP_LOG_ERROR("LWOB: Unexpected TVAL chunk"); + // diffuse strength ... + case AI_LWO_DIFF: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,DIFF,2); + surf.mDiffuseValue = GetU2() / 255.0f; + break; } - break; - } - // texture flags - case AI_LWO_TFLG: - { - AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TFLG,2); - - if (nullptr != pTex) { - const uint16_t s = GetU2(); - if (s & 1) - pTex->majorAxis = LWO::Texture::AXIS_X; - else if (s & 2) - pTex->majorAxis = LWO::Texture::AXIS_Y; - else if (s & 4) - pTex->majorAxis = LWO::Texture::AXIS_Z; - - if (s & 16) { - ASSIMP_LOG_WARN("LWOB: Ignoring \'negate\' flag on texture"); + // specular strength ... + case AI_LWO_SPEC: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SPEC,2); + surf.mSpecularValue = GetU2() / 255.0f; + break; + } + // luminosity ... + case AI_LWO_LUMI: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,LUMI,2); + surf.mLuminosity = GetU2() / 255.0f; + break; + } + // transparency + case AI_LWO_TRAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TRAN,2); + surf.mTransparency = GetU2() / 255.0f; + break; + } + // surface flags + case AI_LWO_FLAG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,FLAG,2); + uint16_t flag = GetU2(); + if (flag & 0x4 ) surf.mMaximumSmoothAngle = 1.56207f; + if (flag & 0x8 ) surf.mColorHighlights = 1.f; + if (flag & 0x100) surf.bDoubleSided = true; + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SMAN,4); + surf.mMaximumSmoothAngle = std::fabs( GetF4() ); + break; + } + // glossiness + case AI_LWO_GLOS: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,GLOS,2); + surf.mGlossiness = (float)GetU2(); + break; + } + // color texture + case AI_LWO_CTEX: + { + pTex = SetupNewTextureLWOB(surf.mColorTextures, + head.length); + break; + } + // diffuse texture + case AI_LWO_DTEX: + { + pTex = SetupNewTextureLWOB(surf.mDiffuseTextures, + head.length); + break; + } + // specular texture + case AI_LWO_STEX: + { + pTex = SetupNewTextureLWOB(surf.mSpecularTextures, + head.length); + break; + } + // bump texture + case AI_LWO_BTEX: + { + pTex = SetupNewTextureLWOB(surf.mBumpTextures, + head.length); + break; + } + // transparency texture + case AI_LWO_TTEX: + { + pTex = SetupNewTextureLWOB(surf.mOpacityTextures, + head.length); + break; + } + // texture path + case AI_LWO_TIMG: + { + if (pTex) { + GetS0(pTex->mFileName,head.length); + } else { + ASSIMP_LOG_WARN("LWOB: Unexpected TIMG chunk"); } + break; } - else { - ASSIMP_LOG_WARN("LWOB: Unexpected TFLG chunk"); + // texture strength + case AI_LWO_TVAL: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TVAL,1); + if (pTex) { + pTex->mStrength = (float)GetU1()/ 255.f; + } else { + ASSIMP_LOG_ERROR("LWOB: Unexpected TVAL chunk"); + } + break; + } + // texture flags + case AI_LWO_TFLG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TFLG,2); + + if (nullptr != pTex) { + const uint16_t s = GetU2(); + if (s & 1) + pTex->majorAxis = LWO::Texture::AXIS_X; + else if (s & 2) + pTex->majorAxis = LWO::Texture::AXIS_Y; + else if (s & 4) + pTex->majorAxis = LWO::Texture::AXIS_Z; + + if (s & 16) { + ASSIMP_LOG_WARN("LWOB: Ignoring \'negate\' flag on texture"); + } + } + else { + ASSIMP_LOG_WARN("LWOB: Unexpected TFLG chunk"); + } + break; } - break; - } } mFileBuffer = next; } diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOFileData.h b/Engine/lib/assimp/code/AssetLib/LWO/LWOFileData.h index 656dd4529..c81111251 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOFileData.h +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.cpp b/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.cpp index df0ba2238..3d2d57e77 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AssetLib/LWO/LWOLoader.h" #include "PostProcessing/ConvertToLHProcess.h" #include "PostProcessing/ProcessHelper.h" +#include "Geometry/GeometryUtils.h" #include #include @@ -135,7 +134,7 @@ void LWOImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open LWO file ", pFile, "."); } @@ -178,7 +177,7 @@ void LWOImporter::InternReadFile(const std::string &pFile, mLayers->push_back(Layer()); mCurLayer = &mLayers->back(); mCurLayer->mName = ""; - mCurLayer->mIndex = (uint16_t) -1; + mCurLayer->mIndex = 1; // old lightwave file format (prior to v6) mIsLWO2 = false; @@ -215,7 +214,7 @@ void LWOImporter::InternReadFile(const std::string &pFile, } else { mIsLWO2 = true; } - + LoadLWO2File(); // The newer lightwave format allows the user to configure the @@ -398,14 +397,6 @@ void LWOImporter::InternReadFile(const std::string &pFile, pvVC[w]++; } -#if 0 - // process vertex weights. We can't properly reconstruct the whole skeleton for now, - // but we can create dummy bones for all weight channels which we have. - for (unsigned int w = 0; w < layer.mWeightChannels.size();++w) - { - } -#endif - face.mIndices[q] = vert; } pf->mIndices = face.mIndices; @@ -429,7 +420,7 @@ void LWOImporter::InternReadFile(const std::string &pFile, // Generate nodes to render the mesh. Store the source layer in the mParent member of the nodes unsigned int num = static_cast(apcMeshes.size() - meshStart); if (layer.mName != "" || num > 0) { - aiNode *pcNode = new aiNode(); + std::unique_ptr pcNode(new aiNode()); pcNode->mName.Set(layer.mName); pcNode->mParent = (aiNode *)&layer; pcNode->mNumMeshes = num; @@ -439,7 +430,8 @@ void LWOImporter::InternReadFile(const std::string &pFile, for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p) pcNode->mMeshes[p] = p + meshStart; } - apcNodes[layer.mIndex] = pcNode; + ASSIMP_LOG_DEBUG("insert apcNode for layer ", layer.mIndex, " \"", layer.mName, "\""); + apcNodes[layer.mIndex] = pcNode.release(); } } @@ -564,6 +556,7 @@ void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector & } } } + GeometryUtils::normalizeVectorArray(mesh->mNormals, mesh->mNormals, mesh->mNumVertices); } // ------------------------------------------------------------------------------------------------ @@ -572,40 +565,64 @@ void LWOImporter::GenerateNodeGraph(std::map &apcNodes) { aiNode *root = mScene->mRootNode = new aiNode(); root->mName.Set(""); - //Set parent of all children, inserting pivots - std::map mapPivot; - for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) { - - //Get the parent index - LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent); - uint16_t parentIndex = nodeLayer->mParent; - - //Create pivot node, store it into the pivot map, and set the parent as the pivot - aiNode *pivotNode = new aiNode(); - pivotNode->mName.Set("Pivot-" + std::string(itapcNodes->second->mName.data)); - itapcNodes->second->mParent = pivotNode; - - //Look for the parent node to attach the pivot to - if (apcNodes.find(parentIndex) != apcNodes.end()) { - pivotNode->mParent = apcNodes[parentIndex]; - } else { - //If not, attach to the root node - pivotNode->mParent = root; - } - - //Set the node and the pivot node transformation - itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x; - itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y; - itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z; - pivotNode->mTransformation.a4 = nodeLayer->mPivot.x; - pivotNode->mTransformation.b4 = nodeLayer->mPivot.y; - pivotNode->mTransformation.c4 = nodeLayer->mPivot.z; - mapPivot[-(itapcNodes->first + 2)] = pivotNode; + ASSIMP_LOG_DEBUG("apcNodes initial size: ", apcNodes.size()); + if (!apcNodes.empty()) { + ASSIMP_LOG_DEBUG("first apcNode is: ", apcNodes.begin()->first, " \"", apcNodes.begin()->second->mName.C_Str(), "\""); } - //Merge pivot map into node map - for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) { - apcNodes[itMapPivot->first] = itMapPivot->second; + //Set parent of all children, inserting pivots + { + std::map mapPivot; + for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) { + + //Get the parent index + LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent); + uint16_t parentIndex = nodeLayer->mParent; + + //Create pivot node, store it into the pivot map, and set the parent as the pivot + std::unique_ptr pivotNode(new aiNode()); + pivotNode->mName.Set("Pivot-" + std::string(itapcNodes->second->mName.data)); + itapcNodes->second->mParent = pivotNode.get(); + + //Look for the parent node to attach the pivot to + if (apcNodes.find(parentIndex) != apcNodes.end()) { + pivotNode->mParent = apcNodes[parentIndex]; + } else { + //If not, attach to the root node + pivotNode->mParent = root; + } + + //Set the node and the pivot node transformation + itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x; + itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y; + itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z; + pivotNode->mTransformation.a4 = nodeLayer->mPivot.x; + pivotNode->mTransformation.b4 = nodeLayer->mPivot.y; + pivotNode->mTransformation.c4 = nodeLayer->mPivot.z; + uint16_t pivotNodeId = static_cast(-(itapcNodes->first + 2)); + ASSIMP_LOG_DEBUG("insert pivot node: ", pivotNodeId); + auto oldNodeIt = mapPivot.find(pivotNodeId); + if (oldNodeIt != mapPivot.end()) { + ASSIMP_LOG_ERROR("attempted to insert pivot node which already exists in pivot map ", pivotNodeId, " \"", pivotNode->mName.C_Str(), "\""); + } else { + mapPivot.emplace(pivotNodeId, pivotNode.release()); + } + } + + ASSIMP_LOG_DEBUG("pivot nodes: ", mapPivot.size()); + //Merge pivot map into node map + for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end();) { + uint16_t pivotNodeId = itMapPivot->first; + auto oldApcNodeIt = apcNodes.find(pivotNodeId); + if (oldApcNodeIt != apcNodes.end()) { + ASSIMP_LOG_ERROR("attempted to insert pivot node which already exists in apc nodes ", pivotNodeId, " \"", itMapPivot->second->mName.C_Str(), "\""); + } else { + apcNodes.emplace(pivotNodeId, itMapPivot->second); + } + itMapPivot->second = nullptr; + itMapPivot = mapPivot.erase(itMapPivot); + } + ASSIMP_LOG_DEBUG("total nodes: ", apcNodes.size()); } //Set children of all parents @@ -627,8 +644,15 @@ void LWOImporter::GenerateNodeGraph(std::map &apcNodes) { } } - if (!mScene->mRootNode->mNumChildren) + if (!mScene->mRootNode->mNumChildren) { + ASSIMP_LOG_DEBUG("All apcNodes:"); + for (auto nodeIt = apcNodes.begin(); nodeIt != apcNodes.end(); ) { + ASSIMP_LOG_DEBUG("Node ", nodeIt->first, " \"", nodeIt->second->mName.C_Str(), "\""); + nodeIt->second = nullptr; + nodeIt = apcNodes.erase(nodeIt); + } throw DeadlyImportError("LWO: Unable to build a valid node graph"); + } // Remove a single root node with no meshes assigned to it ... if (1 == mScene->mRootNode->mNumChildren) { @@ -1462,7 +1486,6 @@ void LWOImporter::LoadLWO2File() { if (mFileBuffer + head.length > end) { throw DeadlyImportError("LWO2: Chunk length points behind the file"); - break; } uint8_t *const next = mFileBuffer + head.length; mFileBuffer += bufOffset; diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.h b/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.h index 9e116a3cc..3f81ff449 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.h +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/LWO/LWOMaterial.cpp b/Engine/lib/assimp/code/AssetLib/LWO/LWOMaterial.cpp index 50bac884b..1d7d137e1 100644 --- a/Engine/lib/assimp/code/AssetLib/LWO/LWOMaterial.cpp +++ b/Engine/lib/assimp/code/AssetLib/LWO/LWOMaterial.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -345,7 +345,7 @@ void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) { // (the diffuse value is just a scaling factor) // If a diffuse texture is set, we set this value to 1.0 - clr = (b && false ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor); + clr = (b ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor); clr.r *= surf.mDiffuseValue; clr.g *= surf.mDiffuseValue; clr.b *= surf.mDiffuseValue; diff --git a/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.cpp b/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.cpp index c5de55035..226615a4a 100644 --- a/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,7 +63,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "LightWave Scene Importer", "", "", @@ -78,14 +78,22 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Recursive parsing of LWS files -void LWS::Element::Parse(const char *&buffer) { - for (; SkipSpacesAndLineEnd(&buffer); SkipLine(&buffer)) { +namespace { + constexpr int MAX_DEPTH = 1000; // Define the maximum depth allowed +} + +void LWS::Element::Parse(const char *&buffer, const char *end, int depth) { + if (depth > MAX_DEPTH) { + throw std::runtime_error("Maximum recursion depth exceeded in LWS::Element::Parse"); + } + + for (; SkipSpacesAndLineEnd(&buffer, end); SkipLine(&buffer, end)) { // begin of a new element with children bool sub = false; if (*buffer == '{') { ++buffer; - SkipSpaces(&buffer); + SkipSpaces(&buffer, end); sub = true; } else if (*buffer == '}') return; @@ -98,16 +106,15 @@ void LWS::Element::Parse(const char *&buffer) { while (!IsSpaceOrNewLine(*buffer)) ++buffer; children.back().tokens[0] = std::string(cur, (size_t)(buffer - cur)); - SkipSpaces(&buffer); + SkipSpaces(&buffer, end); if (children.back().tokens[0] == "Plugin") { ASSIMP_LOG_VERBOSE_DEBUG("LWS: Skipping over plugin-specific data"); // strange stuff inside Plugin/Endplugin blocks. Needn't // follow LWS syntax, so we skip over it - for (; SkipSpacesAndLineEnd(&buffer); SkipLine(&buffer)) { + for (; SkipSpacesAndLineEnd(&buffer, end); SkipLine(&buffer, end)) { if (!::strncmp(buffer, "EndPlugin", 9)) { - //SkipLine(&buffer); break; } } @@ -122,7 +129,7 @@ void LWS::Element::Parse(const char *&buffer) { // parse more elements recursively if (sub) { - children.back().Parse(buffer); + children.back().Parse(buffer, end, depth + 1); } } } @@ -139,10 +146,6 @@ LWSImporter::LWSImporter() : // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -LWSImporter::~LWSImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool LWSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -159,6 +162,8 @@ const aiImporterDesc *LWSImporter::GetInfo() const { return &desc; } +static constexpr int MagicHackNo = 150392; + // ------------------------------------------------------------------------------------------------ // Setup configuration properties void LWSImporter::SetupProperties(const Importer *pImp) { @@ -167,11 +172,11 @@ void LWSImporter::SetupProperties(const Importer *pImp) { // AI_CONFIG_IMPORT_LWS_ANIM_START first = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_START, - 150392 /* magic hack */); + MagicHackNo /* magic hack */); // AI_CONFIG_IMPORT_LWS_ANIM_END last = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_END, - 150392 /* magic hack */); + MagicHackNo /* magic hack */); if (last < first) { std::swap(last, first); @@ -195,15 +200,16 @@ void LWSImporter::ReadEnvelope(const LWS::Element &dad, LWO::Envelope &fill) { for (++it; it != dad.children.end(); ++it) { const char *c = (*it).tokens[1].c_str(); + const char *end = c + (*it).tokens[1].size(); if ((*it).tokens[0] == "Key") { fill.keys.emplace_back(); LWO::Key &key = fill.keys.back(); float f; - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, key.value); - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, f); key.time = f; @@ -235,13 +241,13 @@ void LWSImporter::ReadEnvelope(const LWS::Element &dad, LWO::Envelope &fill) { ASSIMP_LOG_ERROR("LWS: Unknown span type"); } for (unsigned int i = 0; i < num; ++i) { - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, key.params[i]); } } else if ((*it).tokens[0] == "Behaviors") { - SkipSpaces(&c); + SkipSpaces(&c, end); fill.pre = (LWO::PrePostBehaviour)strtoul10(c, &c); - SkipSpaces(&c); + SkipSpaces(&c, end); fill.post = (LWO::PrePostBehaviour)strtoul10(c, &c); } } @@ -249,47 +255,45 @@ void LWSImporter::ReadEnvelope(const LWS::Element &dad, LWO::Envelope &fill) { // ------------------------------------------------------------------------------------------------ // Read animation channels in the old LightWave animation format -void LWSImporter::ReadEnvelope_Old( - std::list::const_iterator &it, - const std::list::const_iterator &end, - LWS::NodeDesc &nodes, - unsigned int /*version*/) { - unsigned int num, sub_num; - if (++it == end) goto unexpected_end; +void LWSImporter::ReadEnvelope_Old(std::list::const_iterator &it,const std::list::const_iterator &endIt, + LWS::NodeDesc &nodes, unsigned int) { + if (++it == endIt) { + ASSIMP_LOG_ERROR("LWS: Encountered unexpected end of file while parsing object motion"); + return; + } - num = strtoul10((*it).tokens[0].c_str()); + const unsigned int num = strtoul10((*it).tokens[0].c_str()); for (unsigned int i = 0; i < num; ++i) { - nodes.channels.emplace_back(); LWO::Envelope &envl = nodes.channels.back(); envl.index = i; envl.type = (LWO::EnvelopeType)(i + 1); - if (++it == end) { - goto unexpected_end; + if (++it == endIt) { + ASSIMP_LOG_ERROR("LWS: Encountered unexpected end of file while parsing object motion"); + return; } - sub_num = strtoul10((*it).tokens[0].c_str()); - + + const unsigned int sub_num = strtoul10((*it).tokens[0].c_str()); for (unsigned int n = 0; n < sub_num; ++n) { - - if (++it == end) goto unexpected_end; + if (++it == endIt) { + ASSIMP_LOG_ERROR("LWS: Encountered unexpected end of file while parsing object motion"); + return; + } // parse value and time, skip the rest for the moment. LWO::Key key; const char *c = fast_atoreal_move((*it).tokens[0].c_str(), key.value); - SkipSpaces(&c); + const char *end = c + (*it).tokens[0].size(); + SkipSpaces(&c, end); float f; fast_atoreal_move((*it).tokens[0].c_str(), f); key.time = f; - envl.keys.push_back(key); + envl.keys.emplace_back(key); } } - return; - -unexpected_end: - ASSIMP_LOG_ERROR("LWS: Encountered unexpected end of file while parsing object motion"); } // ------------------------------------------------------------------------------------------------ @@ -300,7 +304,6 @@ void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { // the name depends on the type. We break LWS's strange naming convention // and return human-readable, but still machine-parsable and unique, strings. if (src.type == LWS::NodeDesc::OBJECT) { - if (src.path.length()) { std::string::size_type s = src.path.find_last_of("\\/"); if (s == std::string::npos) { @@ -310,14 +313,14 @@ void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { } std::string::size_type t = src.path.substr(s).find_last_of('.'); - nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); - if (nd->mName.length > MAXLEN) { - nd->mName.length = MAXLEN; + nd->mName.length = ::ai_snprintf(nd->mName.data, AI_MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); + if (nd->mName.length > AI_MAXLEN) { + nd->mName.length = AI_MAXLEN; } return; } } - nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.name, combined); + nd->mName.length = ::ai_snprintf(nd->mName.data, AI_MAXLEN, "%s_(%08X)", src.name, combined); } // ------------------------------------------------------------------------------------------------ @@ -494,7 +497,7 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open LWS file ", pFile, "."); } @@ -505,7 +508,8 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // Parse the file structure LWS::Element root; const char *dummy = &mBuffer[0]; - root.Parse(dummy); + const char *dummyEnd = dummy + mBuffer.size(); + root.Parse(dummy, dummyEnd); // Construct a Batch-importer to read more files recursively BatchLoader batch(pIOHandler); @@ -544,6 +548,7 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // Now read all elements in a very straightforward manner for (; it != root.children.end(); ++it) { const char *c = (*it).tokens[1].c_str(); + const char *end = c + (*it).tokens[1].size(); // 'FirstFrame': begin of animation slice if ((*it).tokens[0] == "FirstFrame") { @@ -571,14 +576,14 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy LWS::NodeDesc d; d.type = LWS::NodeDesc::OBJECT; if (version >= 4) { // handle LWSC 4 explicit ID - SkipSpaces(&c); + SkipSpaces(&c, end); d.number = strtoul16(c, &c) & AI_LWS_MASK; } else { d.number = cur_object++; } // and add the file to the import list - SkipSpaces(&c); + SkipSpaces(&c, end); std::string path = FindLWOFile(c); d.path = path; d.id = batch.AddLoadRequest(path, 0, &props); @@ -592,7 +597,7 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy if (version >= 4) { // handle LWSC 4 explicit ID d.number = strtoul16(c, &c) & AI_LWS_MASK; - SkipSpaces(&c); + SkipSpaces(&c, end); } else { d.number = cur_object++; } @@ -608,7 +613,7 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy d.type = LWS::NodeDesc::OBJECT; if (version >= 4) { // handle LWSC 4 explicit ID d.number = strtoul16(c, &c) & AI_LWS_MASK; - SkipSpaces(&c); + SkipSpaces(&c, end); } else { d.number = cur_object++; } @@ -632,18 +637,17 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy nodes.push_back(d); } ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Channel\'"); + } else { + // important: index of channel + nodes.back().channels.emplace_back(); + LWO::Envelope &env = nodes.back().channels.back(); + + env.index = strtoul10(c); + + // currently we can just interpret the standard channels 0...9 + // (hack) assume that index-i yields the binary channel type from LWO + env.type = (LWO::EnvelopeType)(env.index + 1); } - - // important: index of channel - nodes.back().channels.emplace_back(); - LWO::Envelope &env = nodes.back().channels.back(); - - env.index = strtoul10(c); - - // currently we can just interpret the standard channels 0...9 - // (hack) assume that index-i yields the binary channel type from LWO - env.type = (LWO::EnvelopeType)(env.index + 1); - } // 'Envelope': a single animation channel else if ((*it).tokens[0] == "Envelope") { @@ -673,26 +677,25 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // two ints per envelope LWO::Envelope &env = *envelopeIt; env.pre = (LWO::PrePostBehaviour)strtoul10(c, &c); - SkipSpaces(&c); + SkipSpaces(&c, end); env.post = (LWO::PrePostBehaviour)strtoul10(c, &c); - SkipSpaces(&c); + SkipSpaces(&c, end); } } } // 'ParentItem': specifies the parent of the current element else if ((*it).tokens[0] == "ParentItem") { - if (nodes.empty()) + if (nodes.empty()) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'ParentItem\'"); - - else + } else { nodes.back().parent = strtoul16(c, &c); + } } // 'ParentObject': deprecated one for older formats else if (version < 3 && (*it).tokens[0] == "ParentObject") { - if (nodes.empty()) + if (nodes.empty()) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'ParentObject\'"); - - else { + } else { nodes.back().parent = strtoul10(c, &c) | (1u << 28u); } } @@ -705,19 +708,20 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy if (version >= 4) { // handle LWSC 4 explicit ID d.number = strtoul16(c, &c) & AI_LWS_MASK; - } else + } else { d.number = cur_camera++; + } nodes.push_back(d); num_camera++; } // 'CameraName': set name of currently active camera else if ((*it).tokens[0] == "CameraName") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'CameraName\'"); - - else + } else { nodes.back().name = c; + } } // 'AddLight': add a light to the scenegraph else if ((*it).tokens[0] == "AddLight") { @@ -728,19 +732,20 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy if (version >= 4) { // handle LWSC 4 explicit ID d.number = strtoul16(c, &c) & AI_LWS_MASK; - } else + } else { d.number = cur_light++; + } nodes.push_back(d); num_light++; } // 'LightName': set name of currently active light else if ((*it).tokens[0] == "LightName") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightName\'"); - - else + } else { nodes.back().name = c; + } } // 'LightIntensity': set intensity of currently active light else if ((*it).tokens[0] == "LightIntensity" || (*it).tokens[0] == "LgtIntensity") { @@ -758,62 +763,58 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } // 'LightType': set type of currently active light else if ((*it).tokens[0] == "LightType") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightType\'"); - - else + } else { nodes.back().lightType = strtoul10(c); - + } } // 'LightFalloffType': set falloff type of currently active light else if ((*it).tokens[0] == "LightFalloffType") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightFalloffType\'"); - else + } else { nodes.back().lightFalloffType = strtoul10(c); - + } } // 'LightConeAngle': set cone angle of currently active light else if ((*it).tokens[0] == "LightConeAngle") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightConeAngle\'"); - - else + } else { nodes.back().lightConeAngle = fast_atof(c); - + } } // 'LightEdgeAngle': set area where we're smoothing from min to max intensity else if ((*it).tokens[0] == "LightEdgeAngle") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightEdgeAngle\'"); - - else + } else { nodes.back().lightEdgeAngle = fast_atof(c); - + } } // 'LightColor': set color of currently active light else if ((*it).tokens[0] == "LightColor") { - if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) + if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightColor\'"); - - else { + } else { c = fast_atoreal_move(c, (float &)nodes.back().lightColor.r); - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, (float &)nodes.back().lightColor.g); - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, (float &)nodes.back().lightColor.b); } } // 'PivotPosition': position of local transformation origin else if ((*it).tokens[0] == "PivotPosition" || (*it).tokens[0] == "PivotPoint") { - if (nodes.empty()) + if (nodes.empty()) { ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'PivotPosition\'"); - else { + } else { c = fast_atoreal_move(c, (float &)nodes.back().pivotPos.x); - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, (float &)nodes.back().pivotPos.y); - SkipSpaces(&c); + SkipSpaces(&c, end); c = fast_atoreal_move(c, (float &)nodes.back().pivotPos.z); // Mark pivotPos as set nodes.back().isPivotSet = true; @@ -823,7 +824,6 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // resolve parenting for (std::list::iterator ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { - // check whether there is another node which calls us a parent for (std::list::iterator dit = nodes.begin(); dit != nodes.end(); ++dit) { if (dit != ndIt && *ndIt == (*dit).parent) { @@ -859,7 +859,7 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy aiNode *nd = master->mRootNode = new aiNode(); // allocate storage for cameras&lights - if (num_camera) { + if (num_camera > 0u) { master->mCameras = new aiCamera *[master->mNumCameras = num_camera]; } aiCamera **cams = master->mCameras; diff --git a/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.h b/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.h index 3df9fe9d9..d8b718655 100644 --- a/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.h +++ b/Engine/lib/assimp/code/AssetLib/LWS/LWSLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -76,7 +76,7 @@ public: std::list children; //! Recursive parsing function - void Parse(const char *&buffer); + void Parse(const char *&buffer, const char *end, int depth = 0); }; #define AI_LWS_MASK (0xffffffff >> 4u) @@ -174,7 +174,7 @@ struct NodeDesc { class LWSImporter : public BaseImporter { public: LWSImporter(); - ~LWSImporter() override; + ~LWSImporter() override = default; // ------------------------------------------------------------------- // Check whether we can read a specific file diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.cpp b/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.cpp index cf87b6221..5545be415 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.h b/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.h index d77743f56..0e9ab4305 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.h +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.cpp b/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.cpp index 895b2bf70..b74b72dc8 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. @@ -85,7 +85,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. are listed in aiScene->mRootNode->children, but all without meshes */ -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Model 3D Importer", "", "", diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.h b/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.h index 5d3fcaa7b..d9e546f39 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.h +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. @@ -65,7 +65,7 @@ class M3DImporter : public BaseImporter { public: /// \brief Default constructor M3DImporter(); - ~M3DImporter() override {} + ~M3DImporter() override = default; /// \brief Returns whether the class can handle the format of the given file. /// \remark See BaseImporter::CanRead() for details. diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DMaterials.h b/Engine/lib/assimp/code/AssetLib/M3D/M3DMaterials.h index a1b0fd742..dc87a99c7 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DMaterials.h +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DMaterials.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.cpp b/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.cpp index 30452c776..3741bbca3 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.cpp +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. @@ -39,8 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER -#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) +#if !defined ASSIMP_BUILD_NO_M3D_IMPORTER || !(defined ASSIMP_BUILD_NO_EXPORT || defined ASSIMP_BUILD_NO_M3D_EXPORTER) #include "M3DWrapper.h" @@ -149,4 +148,3 @@ void M3DWrapper::ClearSave() { } // namespace Assimp #endif -#endif diff --git a/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.h b/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.h index c75ff1027..8a3fd92be 100644 --- a/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.h +++ b/Engine/lib/assimp/code/AssetLib/M3D/M3DWrapper.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team Copyright (c) 2019 bzt All rights reserved. @@ -47,8 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_M3DWRAPPER_H_INC #define AI_M3DWRAPPER_H_INC -#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER -#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) +#if !defined ASSIMP_BUILD_NO_M3D_IMPORTER || !(defined ASSIMP_BUILD_NO_EXPORT || defined ASSIMP_BUILD_NO_M3D_EXPORTER) #include #include @@ -126,7 +125,6 @@ inline m3d_t *M3DWrapper::M3D() const { } // namespace Assimp -#endif #endif // ASSIMP_BUILD_NO_M3D_IMPORTER #endif // AI_M3DWRAPPER_H_INC diff --git a/Engine/lib/assimp/code/AssetLib/MD2/MD2FileData.h b/Engine/lib/assimp/code/AssetLib/MD2/MD2FileData.h index 3bce8feee..0dba71e56 100644 --- a/Engine/lib/assimp/code/AssetLib/MD2/MD2FileData.h +++ b/Engine/lib/assimp/code/AssetLib/MD2/MD2FileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,7 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { namespace MD2 { -// to make it easier for us, we test the magic word against both "endianesses" +// to make it easier for us, we test the magic word against both "endiannesses" #define AI_MD2_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDP2") #define AI_MD2_MAGIC_NUMBER_LE AI_MAKE_MAGIC("2PDI") diff --git a/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.cpp b/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.cpp index d3c9c67c9..99dc70d08 100644 --- a/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,7 +39,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ - #ifndef ASSIMP_BUILD_NO_MD2_IMPORTER /** @file Implementation of the MD2 importer class */ @@ -65,7 +62,7 @@ using namespace Assimp::MD2; # define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0]))) #endif -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Quake II Mesh Importer", "", "", @@ -79,7 +76,7 @@ static const aiImporterDesc desc = { }; // ------------------------------------------------------------------------------------------------ -// Helper function to lookup a normal in Quake 2's precalculated table +// Helper function to lookup a normal in Quake 2's pre-calculated table void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut) { // make sure the normal index has a valid value @@ -100,10 +97,6 @@ MD2Importer::MD2Importer() fileSize() {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MD2Importer::~MD2Importer() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const @@ -205,7 +198,7 @@ void MD2Importer::InternReadFile( const std::string& pFile, std::unique_ptr file( pIOHandler->Open( pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open MD2 file ", pFile, ""); } diff --git a/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.h b/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.h index a49ccb2fd..5ac34ad4c 100644 --- a/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.h +++ b/Engine/lib/assimp/code/AssetLib/MD2/MD2Loader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,7 +63,7 @@ using namespace MD2; class MD2Importer : public BaseImporter { public: MD2Importer(); - ~MD2Importer() override; + ~MD2Importer() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/MD2/MD2NormalTable.h b/Engine/lib/assimp/code/AssetLib/MD2/MD2NormalTable.h index 1837939e8..7d13b9ad1 100644 --- a/Engine/lib/assimp/code/AssetLib/MD2/MD2NormalTable.h +++ b/Engine/lib/assimp/code/AssetLib/MD2/MD2NormalTable.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MD3/MD3FileData.h b/Engine/lib/assimp/code/AssetLib/MD3/MD3FileData.h index 01475e679..86d2647b6 100644 --- a/Engine/lib/assimp/code/AssetLib/MD3/MD3FileData.h +++ b/Engine/lib/assimp/code/AssetLib/MD3/MD3FileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,7 +62,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { namespace MD3 { -// to make it easier for us, we test the magic word against both "endianesses" +// to make it easier for us, we test the magic word against both "endiannesses" #define AI_MD3_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDP3") #define AI_MD3_MAGIC_NUMBER_LE AI_MAKE_MAGIC("3PDI") diff --git a/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.cpp b/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.cpp index eae580ed8..7a34ae1ad 100644 --- a/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -70,7 +70,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Quake III Mesh Importer", "", "", @@ -109,7 +109,7 @@ Q3Shader::BlendFunc StringToBlendFunc(const std::string &m) { // Load a Quake 3 shader bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem *io) { std::unique_ptr file(io->Open(pFile, "rt")); - if (!file.get()) + if (!file) return false; // if we can't access the file, don't worry and return ASSIMP_LOG_INFO("Loading Quake3 shader file ", pFile); @@ -123,12 +123,12 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * // remove comments from it (C++ style) CommentRemover::RemoveLineComments("//", &_buff[0]); const char *buff = &_buff[0]; - + const char *end = buff + _buff.size(); Q3Shader::ShaderDataBlock *curData = nullptr; Q3Shader::ShaderMapBlock *curMap = nullptr; // read line per line - for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + for (; SkipSpacesAndLineEnd(&buff, end); SkipLine(&buff, end)) { if (*buff == '{') { ++buff; @@ -140,21 +140,21 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * } // read this data section - for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + for (; SkipSpacesAndLineEnd(&buff, end); SkipLine(&buff, end)) { if (*buff == '{') { ++buff; // add new map section curData->maps.emplace_back(); curMap = &curData->maps.back(); - for (; SkipSpacesAndLineEnd(&buff); SkipLine(&buff)) { + for (; SkipSpacesAndLineEnd(&buff, end); SkipLine(&buff, end)) { // 'map' - Specifies texture file name if (TokenMatchI(buff, "map", 3) || TokenMatchI(buff, "clampmap", 8)) { - curMap->name = GetNextToken(buff); + curMap->name = GetNextToken(buff, end); } // 'blendfunc' - Alpha blending mode else if (TokenMatchI(buff, "blendfunc", 9)) { - const std::string blend_src = GetNextToken(buff); + const std::string blend_src = GetNextToken(buff, end); if (blend_src == "add") { curMap->blend_src = Q3Shader::BLEND_GL_ONE; curMap->blend_dest = Q3Shader::BLEND_GL_ONE; @@ -166,12 +166,12 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA; } else { curMap->blend_src = StringToBlendFunc(blend_src); - curMap->blend_dest = StringToBlendFunc(GetNextToken(buff)); + curMap->blend_dest = StringToBlendFunc(GetNextToken(buff, end)); } } // 'alphafunc' - Alpha testing mode else if (TokenMatchI(buff, "alphafunc", 9)) { - const std::string at = GetNextToken(buff); + const std::string at = GetNextToken(buff, end); if (at == "GT0") { curMap->alpha_test = Q3Shader::AT_GT0; } else if (at == "LT128") { @@ -186,7 +186,6 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * break; } } - } else if (*buff == '}') { ++buff; curData = nullptr; @@ -195,7 +194,7 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * // 'cull' specifies culling behaviour for the model else if (TokenMatchI(buff, "cull", 4)) { - SkipSpaces(&buff); + SkipSpaces(&buff, end); if (!ASSIMP_strincmp(buff, "back", 4)) { // render face's backside, does not function in Q3 engine (bug) curData->cull = Q3Shader::CULL_CCW; } else if (!ASSIMP_strincmp(buff, "front", 5)) { // is not valid keyword in Q3, but occurs in shaders @@ -213,9 +212,10 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * curData = &fill.blocks.back(); // get the name of this section - curData->name = GetNextToken(buff); + curData->name = GetNextToken(buff, end); } } + return true; } @@ -223,7 +223,7 @@ bool Q3Shader::LoadShader(ShaderData &fill, const std::string &pFile, IOSystem * // Load a Quake 3 skin bool Q3Shader::LoadSkin(SkinData &fill, const std::string &pFile, IOSystem *io) { std::unique_ptr file(io->Open(pFile, "rt")); - if (!file.get()) + if (!file) return false; // if we can't access the file, don't worry and return ASSIMP_LOG_INFO("Loading Quake3 skin file ", pFile); @@ -232,6 +232,7 @@ bool Q3Shader::LoadSkin(SkinData &fill, const std::string &pFile, IOSystem *io) const size_t s = file->FileSize(); std::vector _buff(s + 1); const char *buff = &_buff[0]; + const char *end = buff + _buff.size(); file->Read(&_buff[0], s, 1); _buff[s] = 0; @@ -240,10 +241,10 @@ bool Q3Shader::LoadSkin(SkinData &fill, const std::string &pFile, IOSystem *io) // read token by token and fill output table for (; *buff;) { - SkipSpacesAndLineEnd(&buff); + SkipSpacesAndLineEnd(&buff, end); // get first identifier - std::string ss = GetNextToken(buff); + std::string ss = GetNextToken(buff, end); // ignore tokens starting with tag_ if (!::strncmp(&ss[0], "tag_", std::min((size_t)4, ss.length()))) @@ -253,8 +254,9 @@ bool Q3Shader::LoadSkin(SkinData &fill, const std::string &pFile, IOSystem *io) SkinData::TextureEntry &entry = fill.textures.back(); entry.first = ss; - entry.second = GetNextToken(buff); + entry.second = GetNextToken(buff, end); } + return true; } @@ -293,7 +295,7 @@ void Q3Shader::ConvertShaderToMaterial(aiMaterial *out, const ShaderDataBlock &s // - in any case: set it as diffuse texture // // If the texture is using 'filter' blending - // - take as lightmap + // - take as light-map // // Textures with alpha funcs // - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set) @@ -709,7 +711,7 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::unique_ptr file(pIOHandler->Open(pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open MD3 file ", pFile, "."); } @@ -722,6 +724,7 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::vector mBuffer2(fileSize); file->Read(&mBuffer2[0], 1, fileSize); mBuffer = &mBuffer2[0]; + const unsigned char* bufferEnd = mBuffer + fileSize; pcHeader = (BE_NCONST MD3::Header *)mBuffer; @@ -747,9 +750,15 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // Navigate to the list of surfaces BE_NCONST MD3::Surface *pcSurfaces = (BE_NCONST MD3::Surface *)(mBuffer + pcHeader->OFS_SURFACES); + if ((const unsigned char*)pcSurfaces + sizeof(MD3::Surface) * pcHeader->NUM_SURFACES > bufferEnd) { + throw DeadlyImportError("MD3 surface headers are outside the file"); + } // Navigate to the list of tags BE_NCONST MD3::Tag *pcTags = (BE_NCONST MD3::Tag *)(mBuffer + pcHeader->OFS_TAGS); + if ((const unsigned char*)pcTags + sizeof(MD3::Tag) * pcHeader->NUM_TAGS > bufferEnd) { + throw DeadlyImportError("MD3 tags are outside the file"); + } // Allocate output storage pScene->mNumMeshes = pcHeader->NUM_SURFACES; @@ -1024,6 +1033,10 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) { aiNode *nd = pScene->mRootNode->mChildren[i] = new aiNode(); + if ((const unsigned char*)pcTags + sizeof(MD3::Tag) > bufferEnd) { + throw DeadlyImportError("MD3 tag is outside the file"); + } + nd->mName.Set((const char *)pcTags->NAME); nd->mParent = pScene->mRootNode; diff --git a/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.h b/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.h index d911bb1da..eee66a3df 100644 --- a/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.h +++ b/Engine/lib/assimp/code/AssetLib/MD3/MD3Loader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.cpp b/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.cpp index 6c6758356..02f08d3ea 100644 --- a/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -64,7 +64,7 @@ using namespace Assimp; // Minimum weight value. Weights inside [-n ... n] are ignored #define AI_MD5_WEIGHT_EPSILON Math::getEpsilon() -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Doom 3 / MD5 Mesh Importer", "", "", @@ -92,10 +92,6 @@ MD5Importer::MD5Importer() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MD5Importer::~MD5Importer() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -214,7 +210,7 @@ void MD5Importer::MakeDataUnique(MD5::MeshDesc &meshSrc) { const unsigned int guess = (unsigned int)(fWeightsPerVert * iNewNum); meshSrc.mWeights.reserve(guess + (guess >> 3)); // + 12.5% as buffer - for (FaceList::const_iterator iter = meshSrc.mFaces.begin(), iterEnd = meshSrc.mFaces.end(); iter != iterEnd; ++iter) { + for (FaceArray::const_iterator iter = meshSrc.mFaces.begin(), iterEnd = meshSrc.mFaces.end(); iter != iterEnd; ++iter) { const aiFace &face = *iter; for (unsigned int i = 0; i < 3; ++i) { if (face.mIndices[0] >= meshSrc.mVertices.size()) { @@ -235,7 +231,7 @@ void MD5Importer::MakeDataUnique(MD5::MeshDesc &meshSrc) { // ------------------------------------------------------------------------------------------------ // Recursive node graph construction from a MD5MESH -void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneList &bones) { +void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneArray &bones) { ai_assert(nullptr != piParent); ai_assert(!piParent->mNumChildren); @@ -286,7 +282,7 @@ void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneList &b // ------------------------------------------------------------------------------------------------ // Recursive node graph construction from a MD5ANIM -void MD5Importer::AttachChilds_Anim(int iParentID, aiNode *piParent, AnimBoneList &bones, const aiNodeAnim **node_anims) { +void MD5Importer::AttachChilds_Anim(int iParentID, aiNode *piParent, AnimBoneArray &bones, const aiNodeAnim **node_anims) { ai_assert(nullptr != piParent); ai_assert(!piParent->mNumChildren); @@ -331,7 +327,7 @@ void MD5Importer::LoadMD5MeshFile() { std::unique_ptr file(mIOHandler->Open(filename, "rb")); // Check whether we can read from the file - if (file.get() == nullptr || !file->FileSize()) { + if (file == nullptr || !file->FileSize()) { ASSIMP_LOG_WARN("Failed to access MD5MESH file: ", filename); return; } @@ -406,7 +402,7 @@ void MD5Importer::LoadMD5MeshFile() { // copy texture coordinates aiVector3D *pv = mesh->mTextureCoords[0]; - for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { pv->x = (*iter).mUV.x; pv->y = 1.0f - (*iter).mUV.y; // D3D to OpenGL pv->z = 0.0f; @@ -416,7 +412,7 @@ void MD5Importer::LoadMD5MeshFile() { unsigned int *piCount = new unsigned int[meshParser.mJoints.size()]; ::memset(piCount, 0, sizeof(unsigned int) * meshParser.mJoints.size()); - for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) { MD5::WeightDesc &weightDesc = meshSrc.mWeights[w]; /* FIX for some invalid exporters */ @@ -451,7 +447,7 @@ void MD5Importer::LoadMD5MeshFile() { } pv = mesh->mVertices; - for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { + for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) { // compute the final vertex position from all single weights *pv = aiVector3D(); @@ -553,7 +549,7 @@ void MD5Importer::LoadMD5AnimFile() { std::unique_ptr file(mIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (!file.get() || !file->FileSize()) { + if (!file || !file->FileSize()) { ASSIMP_LOG_WARN("Failed to read MD5ANIM file: ", pFile); return; } @@ -589,14 +585,14 @@ void MD5Importer::LoadMD5AnimFile() { // 1 tick == 1 frame anim->mTicksPerSecond = animParser.fFrameRate; - for (FrameList::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end(); iter != iterEnd; ++iter) { + for (FrameArray::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end(); iter != iterEnd; ++iter) { double dTime = (double)(*iter).iIndex; aiNodeAnim **pcAnimNode = anim->mChannels; if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin()) /* be sure we have at least one frame */ { // now process all values in there ... read all joints MD5::BaseFrameDesc *pcBaseFrame = &animParser.mBaseFrames[0]; - for (AnimBoneList::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end(); ++iter2, + for (AnimBoneArray::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end(); ++iter2, ++pcAnimNode, ++pcBaseFrame) { if ((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) { @@ -661,7 +657,7 @@ void MD5Importer::LoadMD5CameraFile() { std::unique_ptr file(mIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (!file.get() || !file->FileSize()) { + if (!file || !file->FileSize()) { throw DeadlyImportError("Failed to read MD5CAMERA file: ", pFile); } mHadMD5Camera = true; @@ -711,7 +707,7 @@ void MD5Importer::LoadMD5CameraFile() { for (std::vector::const_iterator it = cuts.begin(); it != cuts.end() - 1; ++it) { aiAnimation *anim = *tmp++ = new aiAnimation(); - anim->mName.length = ::ai_snprintf(anim->mName.data, MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1)); + anim->mName.length = ::ai_snprintf(anim->mName.data, AI_MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1)); anim->mTicksPerSecond = cameraParser.fFrameRate; anim->mChannels = new aiNodeAnim *[anim->mNumChannels = 1]; diff --git a/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.h b/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.h index 63fb1c36b..d64d6f5b8 100644 --- a/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.h +++ b/Engine/lib/assimp/code/AssetLib/MD5/MD5Loader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -65,7 +65,7 @@ using namespace Assimp::MD5; class MD5Importer : public BaseImporter { public: MD5Importer(); - ~MD5Importer() override; + ~MD5Importer() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -118,7 +118,7 @@ protected: * @param node_anims Generated node animations */ void AttachChilds_Anim(int iParentID, aiNode *piParent, - AnimBoneList &bones, const aiNodeAnim **node_anims); + AnimBoneArray &bones, const aiNodeAnim **node_anims); // ------------------------------------------------------------------- /** Construct node hierarchy from a given MD5MESH @@ -126,7 +126,7 @@ protected: * @param piParent Parent node to attach to * @param bones Input bones */ - void AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneList &bones); + void AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneArray &bones); // ------------------------------------------------------------------- /** Build unique vertex buffers from a given MD5ANIM diff --git a/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.cpp b/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.cpp index 606660080..2de8d5033 100644 --- a/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.cpp +++ b/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,14 +58,11 @@ using namespace Assimp::MD5; // ------------------------------------------------------------------------------------------------ // Parse the segment structure for an MD5 file -MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) { +MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) : buffer(_buffer), bufferEnd(nullptr), fileSize(_fileSize), lineNumber(0) { ai_assert(nullptr != _buffer); ai_assert(0 != _fileSize); - buffer = _buffer; - fileSize = _fileSize; - lineNumber = 0; - + bufferEnd = buffer + fileSize; ASSIMP_LOG_DEBUG("MD5Parser begin"); // parse the file header @@ -92,7 +87,7 @@ MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) { // ------------------------------------------------------------------------------------------------ // Report error to the log stream -/*static*/ AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) { +AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) { char szBuffer[1024]; ::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error); throw DeadlyImportError(szBuffer); @@ -100,9 +95,9 @@ MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) { // ------------------------------------------------------------------------------------------------ // Report warning to the log stream -/*static*/ void MD5Parser::ReportWarning(const char *warn, unsigned int line) { +void MD5Parser::ReportWarning(const char *warn, unsigned int line) { char szBuffer[1024]; - ::sprintf(szBuffer, "[MD5] Line %u: %s", line, warn); + ::snprintf(szBuffer, sizeof(szBuffer), "[MD5] Line %u: %s", line, warn); ASSIMP_LOG_WARN(szBuffer); } @@ -120,12 +115,15 @@ void MD5Parser::ParseHeader() { ReportError("MD5 version tag is unknown (10 is expected)"); } SkipLine(); + if (buffer == bufferEnd) { + return; + } // print the command line options to the console // FIX: can break the log length limit, so we need to be careful char *sz = buffer; - while (!IsLineEnd(*buffer++)) - ; + while (!IsLineEnd(*buffer++)); + ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz)))); SkipSpacesAndLineEnd(); } @@ -138,23 +136,41 @@ bool MD5Parser::ParseSection(Section &out) { // first parse the name of the section char *sz = buffer; - while (!IsSpaceOrNewLine(*buffer)) - buffer++; + while (!IsSpaceOrNewLine(*buffer)) { + ++buffer; + if (buffer == bufferEnd) { + return false; + } + } out.mName = std::string(sz, (uintptr_t)(buffer - sz)); - SkipSpaces(); + while (IsSpace(*buffer)) { + ++buffer; + if (buffer == bufferEnd) { + return false; + } + } bool running = true; while (running) { if ('{' == *buffer) { // it is a normal section so read all lines - buffer++; + ++buffer; + if (buffer == bufferEnd) { + return false; + } bool run = true; while (run) { - if (!SkipSpacesAndLineEnd()) { + while (IsSpaceOrNewLine(*buffer)) { + ++buffer; + if (buffer == bufferEnd) { + return false; + } + } + if ('\0' == *buffer) { return false; // seems this was the last section } if ('}' == *buffer) { - buffer++; + ++buffer; break; } @@ -163,89 +179,135 @@ bool MD5Parser::ParseSection(Section &out) { elem.iLineNumber = lineNumber; elem.szStart = buffer; + elem.end = bufferEnd; // terminate the line with zero - while (!IsLineEnd(*buffer)) - buffer++; + while (!IsLineEnd(*buffer)) { + ++buffer; + if (buffer == bufferEnd) { + return false; + } + } if (*buffer) { ++lineNumber; *buffer++ = '\0'; + if (buffer == bufferEnd) { + return false; + } } } break; } else if (!IsSpaceOrNewLine(*buffer)) { // it is an element at global scope. Parse its value and go on sz = buffer; - while (!IsSpaceOrNewLine(*buffer++)) - ; + while (!IsSpaceOrNewLine(*buffer++)) { + if (buffer == bufferEnd) { + return false; + } + } out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz)); continue; } break; } - return SkipSpacesAndLineEnd(); + if (buffer == bufferEnd) { + return false; + } + while (IsSpaceOrNewLine(*buffer)) { + if (buffer == bufferEnd) { + break; + } + ++buffer; + } + return '\0' != *buffer; +} + +// skip all spaces ... handle EOL correctly +inline void AI_MD5_SKIP_SPACES(const char **sz, const char *bufferEnd, int linenumber) { + if (!SkipSpaces(sz, bufferEnd)) { + MD5Parser::ReportWarning("Unexpected end of line", linenumber); + } +} + +// read a triple float in brackets: (1.0 1.0 1.0) +inline void AI_MD5_READ_TRIPLE(aiVector3D &vec, const char **sz, const char *bufferEnd, int linenumber) { + AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); + if ('(' != **sz) { + MD5Parser::ReportWarning("Unexpected token: ( was expected", linenumber); + if (*sz == bufferEnd) + return; + ++*sz; + } + if (*sz == bufferEnd) + return; + ++*sz; + AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); + *sz = fast_atoreal_move(*sz, (float &)vec.x); + AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); + *sz = fast_atoreal_move(*sz, (float &)vec.y); + AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); + *sz = fast_atoreal_move(*sz, (float &)vec.z); + AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); + if (')' != **sz) { + MD5Parser::ReportWarning("Unexpected token: ) was expected", linenumber); + } + if (*sz == bufferEnd) + return; + ++*sz; +} + +// parse a string, enclosed in quotation marks or not +inline bool AI_MD5_PARSE_STRING(const char **sz, const char *bufferEnd, aiString &out, int linenumber) { + bool bQuota = (**sz == '\"'); + const char *szStart = *sz; + while (!IsSpaceOrNewLine(**sz)) { + ++*sz; + if (*sz == bufferEnd) break; + } + const char *szEnd = *sz; + if (bQuota) { + szStart++; + if ('\"' != *(szEnd -= 1)) { + MD5Parser::ReportWarning("Expected closing quotation marks in string", linenumber); + ++*sz; + } + } + out.length = (ai_uint32)(szEnd - szStart); + ::memcpy(out.data, szStart, out.length); + out.data[out.length] = '\0'; + + return true; +} + +// parse a string, enclosed in quotation marks +inline void AI_MD5_PARSE_STRING_IN_QUOTATION(const char **sz, const char *bufferEnd, aiString &out) { + out.length = 0u; + while (('\"' != **sz && '\0' != **sz) && *sz != bufferEnd) { + ++*sz; + } + if ('\0' != **sz) { + const char *szStart = ++(*sz); + + while (('\"' != **sz && '\0' != **sz) && *sz != bufferEnd) { + ++*sz; + } + if ('\0' != **sz) { + const char *szEnd = *sz; + ++*sz; + out.length = (ai_uint32)(szEnd - szStart); + ::memcpy(out.data, szStart, out.length); + } + } + out.data[out.length] = '\0'; } -// ------------------------------------------------------------------------------------------------ -// Some dirty macros just because they're so funny and easy to debug - -// skip all spaces ... handle EOL correctly -#define AI_MD5_SKIP_SPACES() \ - if (!SkipSpaces(&sz)) \ - MD5Parser::ReportWarning("Unexpected end of line", elem.iLineNumber); - -// read a triple float in brackets: (1.0 1.0 1.0) -#define AI_MD5_READ_TRIPLE(vec) \ - AI_MD5_SKIP_SPACES(); \ - if ('(' != *sz++) \ - MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber); \ - AI_MD5_SKIP_SPACES(); \ - sz = fast_atoreal_move(sz, (float &)vec.x); \ - AI_MD5_SKIP_SPACES(); \ - sz = fast_atoreal_move(sz, (float &)vec.y); \ - AI_MD5_SKIP_SPACES(); \ - sz = fast_atoreal_move(sz, (float &)vec.z); \ - AI_MD5_SKIP_SPACES(); \ - if (')' != *sz++) \ - MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber); - -// parse a string, enclosed in quotation marks or not -#define AI_MD5_PARSE_STRING(out) \ - bool bQuota = (*sz == '\"'); \ - const char *szStart = sz; \ - while (!IsSpaceOrNewLine(*sz)) \ - ++sz; \ - const char *szEnd = sz; \ - if (bQuota) { \ - szStart++; \ - if ('\"' != *(szEnd -= 1)) { \ - MD5Parser::ReportWarning("Expected closing quotation marks in string", \ - elem.iLineNumber); \ - continue; \ - } \ - } \ - out.length = (size_t)(szEnd - szStart); \ - ::memcpy(out.data, szStart, out.length); \ - out.data[out.length] = '\0'; - -// parse a string, enclosed in quotation marks -#define AI_MD5_PARSE_STRING_IN_QUOTATION(out) \ - while ('\"' != *sz) \ - ++sz; \ - const char *szStart = ++sz; \ - while ('\"' != *sz) \ - ++sz; \ - const char *szEnd = (sz++); \ - out.length = (ai_uint32)(szEnd - szStart); \ - ::memcpy(out.data, szStart, out.length); \ - out.data[out.length] = '\0'; // ------------------------------------------------------------------------------------------------ // .MD5MESH parsing function -MD5MeshParser::MD5MeshParser(SectionList &mSections) { +MD5MeshParser::MD5MeshParser(SectionArray &mSections) { ASSIMP_LOG_DEBUG("MD5MeshParser begin"); // now parse all sections - for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { if ((*iter).mName == "numMeshes") { mMeshes.reserve(::strtoul10((*iter).mGlobalValue.c_str())); } else if ((*iter).mName == "numJoints") { @@ -257,14 +319,15 @@ MD5MeshParser::MD5MeshParser(SectionList &mSections) { BoneDesc &desc = mJoints.back(); const char *sz = elem.szStart; - AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName); - AI_MD5_SKIP_SPACES(); + AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mName); + + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); // negative values, at least -1, is allowed here desc.mParentIndex = (int)strtol10(sz, &sz); - AI_MD5_READ_TRIPLE(desc.mPositionXYZ); - AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there + AI_MD5_READ_TRIPLE(desc.mPositionXYZ, &sz, elem.end, elem.iLineNumber); + AI_MD5_READ_TRIPLE(desc.mRotationQuat, &sz, elem.end, elem.iLineNumber); // normalized quaternion, so w is not there } } else if ((*iter).mName == "mesh") { mMeshes.emplace_back(); @@ -275,52 +338,52 @@ MD5MeshParser::MD5MeshParser(SectionList &mSections) { // shader attribute if (TokenMatch(sz, "shader", 6)) { - AI_MD5_SKIP_SPACES(); - AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mShader); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); + AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mShader); } // numverts attribute else if (TokenMatch(sz, "numverts", 8)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); desc.mVertices.resize(strtoul10(sz)); } // numtris attribute else if (TokenMatch(sz, "numtris", 7)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); desc.mFaces.resize(strtoul10(sz)); } // numweights attribute else if (TokenMatch(sz, "numweights", 10)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); desc.mWeights.resize(strtoul10(sz)); } // vert attribute // "vert 0 ( 0.394531 0.513672 ) 0 1" else if (TokenMatch(sz, "vert", 4)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); const unsigned int idx = ::strtoul10(sz, &sz); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); if (idx >= desc.mVertices.size()) desc.mVertices.resize(idx + 1); VertexDesc &vert = desc.mVertices[idx]; if ('(' != *sz++) MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); sz = fast_atoreal_move(sz, (float &)vert.mUV.x); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); sz = fast_atoreal_move(sz, (float &)vert.mUV.y); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); if (')' != *sz++) MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); vert.mFirstWeight = ::strtoul10(sz, &sz); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); vert.mNumWeights = ::strtoul10(sz, &sz); } // tri attribute // "tri 0 15 13 12" else if (TokenMatch(sz, "tri", 3)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); const unsigned int idx = strtoul10(sz, &sz); if (idx >= desc.mFaces.size()) desc.mFaces.resize(idx + 1); @@ -328,24 +391,24 @@ MD5MeshParser::MD5MeshParser(SectionList &mSections) { aiFace &face = desc.mFaces[idx]; face.mIndices = new unsigned int[face.mNumIndices = 3]; for (unsigned int i = 0; i < 3; ++i) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); face.mIndices[i] = strtoul10(sz, &sz); } } // weight attribute // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )" else if (TokenMatch(sz, "weight", 6)) { - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); const unsigned int idx = strtoul10(sz, &sz); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); if (idx >= desc.mWeights.size()) desc.mWeights.resize(idx + 1); WeightDesc &weight = desc.mWeights[idx]; weight.mBone = strtoul10(sz, &sz); - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); sz = fast_atoreal_move(sz, weight.mWeight); - AI_MD5_READ_TRIPLE(weight.vOffsetPosition); + AI_MD5_READ_TRIPLE(weight.vOffsetPosition, &sz, elem.end, elem.iLineNumber); } } } @@ -355,12 +418,12 @@ MD5MeshParser::MD5MeshParser(SectionList &mSections) { // ------------------------------------------------------------------------------------------------ // .MD5ANIM parsing function -MD5AnimParser::MD5AnimParser(SectionList &mSections) { +MD5AnimParser::MD5AnimParser(SectionArray &mSections) { ASSIMP_LOG_DEBUG("MD5AnimParser begin"); fFrameRate = 24.0f; mNumAnimatedComponents = UINT_MAX; - for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { if ((*iter).mName == "hierarchy") { // "sheath" 0 63 6 for (const auto &elem : (*iter).mElements) { @@ -368,18 +431,18 @@ MD5AnimParser::MD5AnimParser(SectionList &mSections) { AnimBoneDesc &desc = mAnimatedBones.back(); const char *sz = elem.szStart; - AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName); - AI_MD5_SKIP_SPACES(); + AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mName); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); // parent index - negative values are allowed (at least -1) desc.mParentIndex = ::strtol10(sz, &sz); // flags (highest is 2^6-1) - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); if (63 < (desc.iFlags = ::strtoul10(sz, &sz))) { MD5Parser::ReportWarning("Invalid flag combination in hierarchy section", elem.iLineNumber); } - AI_MD5_SKIP_SPACES(); + AI_MD5_SKIP_SPACES(& sz, elem.end, elem.iLineNumber); // index of the first animation keyframe component for this joint desc.iFirstKeyIndex = ::strtoul10(sz, &sz); @@ -392,8 +455,8 @@ MD5AnimParser::MD5AnimParser(SectionList &mSections) { mBaseFrames.emplace_back(); BaseFrameDesc &desc = mBaseFrames.back(); - AI_MD5_READ_TRIPLE(desc.vPositionXYZ); - AI_MD5_READ_TRIPLE(desc.vRotationQuat); + AI_MD5_READ_TRIPLE(desc.vPositionXYZ, &sz, elem.end, elem.iLineNumber); + AI_MD5_READ_TRIPLE(desc.vRotationQuat, &sz, elem.end, elem.iLineNumber); } } else if ((*iter).mName == "frame") { if (!(*iter).mGlobalValue.length()) { @@ -413,7 +476,7 @@ MD5AnimParser::MD5AnimParser(SectionList &mSections) { // now read all elements (continuous list of floats) for (const auto &elem : (*iter).mElements) { const char *sz = elem.szStart; - while (SkipSpacesAndLineEnd(&sz)) { + while (SkipSpacesAndLineEnd(&sz, elem.end)) { float f; sz = fast_atoreal_move(sz, f); desc.mValues.push_back(f); @@ -440,11 +503,11 @@ MD5AnimParser::MD5AnimParser(SectionList &mSections) { // ------------------------------------------------------------------------------------------------ // .MD5CAMERA parsing function -MD5CameraParser::MD5CameraParser(SectionList &mSections) { +MD5CameraParser::MD5CameraParser(SectionArray &mSections) { ASSIMP_LOG_DEBUG("MD5CameraParser begin"); fFrameRate = 24.0f; - for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { + for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) { if ((*iter).mName == "numFrames") { frames.reserve(strtoul10((*iter).mGlobalValue.c_str())); } else if ((*iter).mName == "frameRate") { @@ -461,9 +524,9 @@ MD5CameraParser::MD5CameraParser(SectionList &mSections) { frames.emplace_back(); CameraAnimFrameDesc &cur = frames.back(); - AI_MD5_READ_TRIPLE(cur.vPositionXYZ); - AI_MD5_READ_TRIPLE(cur.vRotationQuat); - AI_MD5_SKIP_SPACES(); + AI_MD5_READ_TRIPLE(cur.vPositionXYZ, &sz, elem.end, elem.iLineNumber); + AI_MD5_READ_TRIPLE(cur.vRotationQuat, &sz, elem.end, elem.iLineNumber); + AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber); cur.fFOV = fast_atof(sz); } } diff --git a/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.h b/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.h index 3108655f1..75e3c22f2 100644 --- a/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.h +++ b/Engine/lib/assimp/code/AssetLib/MD5/MD5Parser.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -39,7 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - +#pragma once /** @file MD5Parser.h * @brief Definition of the .MD5 parser class. @@ -51,61 +50,60 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include struct aiFace; -namespace Assimp { -namespace MD5 { +namespace Assimp { +namespace MD5 { // --------------------------------------------------------------------------- /** Represents a single element in a MD5 file * * Elements are always contained in sections. */ -struct Element -{ +struct Element { //! Points to the starting point of the element //! Whitespace at the beginning and at the end have been removed, //! Elements are terminated with \0 char* szStart; + const char *end; + //! Original line number (can be used in error messages //! if a parsing error occurs) unsigned int iLineNumber; }; -typedef std::vector< Element > ElementList; +using ElementArray = std::vector; // --------------------------------------------------------------------------- /** Represents a section of a MD5 file (such as the mesh or the joints section) * * A section is always enclosed in { and } brackets. */ -struct Section -{ +struct Section { //! Original line number (can be used in error messages //! if a parsing error occurs) unsigned int iLineNumber; //! List of all elements which have been parsed in this section. - ElementList mElements; + ElementArray mElements; //! Name of the section std::string mName; //! For global elements: the value of the element as string - //! Iif !length() the section is not a global element + //! if !length() the section is not a global element std::string mGlobalValue; }; -typedef std::vector< Section> SectionList; +using SectionArray = std::vector

; // --------------------------------------------------------------------------- /** Basic information about a joint */ -struct BaseJointDescription -{ +struct BaseJointDescription { //! Name of the bone aiString mName; @@ -116,8 +114,7 @@ struct BaseJointDescription // --------------------------------------------------------------------------- /** Represents a bone (joint) descriptor in a MD5Mesh file */ -struct BoneDesc : BaseJointDescription -{ +struct BoneDesc : BaseJointDescription { //! Absolute position of the bone aiVector3D mPositionXYZ; @@ -137,13 +134,12 @@ struct BoneDesc : BaseJointDescription unsigned int mMap; }; -typedef std::vector< BoneDesc > BoneList; +using BoneArray = std::vector; // --------------------------------------------------------------------------- /** Represents a bone (joint) descriptor in a MD5Anim file */ -struct AnimBoneDesc : BaseJointDescription -{ +struct AnimBoneDesc : BaseJointDescription { //! Flags (AI_MD5_ANIMATION_FLAG_xxx) unsigned int iFlags; @@ -151,35 +147,31 @@ struct AnimBoneDesc : BaseJointDescription unsigned int iFirstKeyIndex; }; -typedef std::vector< AnimBoneDesc > AnimBoneList; - +using AnimBoneArray = std::vector< AnimBoneDesc >; // --------------------------------------------------------------------------- /** Represents a base frame descriptor in a MD5Anim file */ -struct BaseFrameDesc -{ +struct BaseFrameDesc { aiVector3D vPositionXYZ; aiVector3D vRotationQuat; }; -typedef std::vector< BaseFrameDesc > BaseFrameList; +using BaseFrameArray = std::vector; // --------------------------------------------------------------------------- /** Represents a camera animation frame in a MDCamera file */ -struct CameraAnimFrameDesc : BaseFrameDesc -{ +struct CameraAnimFrameDesc : BaseFrameDesc { float fFOV; }; -typedef std::vector< CameraAnimFrameDesc > CameraFrameList; +using CameraFrameArray = std::vector; // --------------------------------------------------------------------------- /** Represents a frame descriptor in a MD5Anim file */ -struct FrameDesc -{ +struct FrameDesc { //! Index of the frame unsigned int iIndex; @@ -187,15 +179,14 @@ struct FrameDesc std::vector< float > mValues; }; -typedef std::vector< FrameDesc > FrameList; +using FrameArray = std::vector; // --------------------------------------------------------------------------- /** Represents a vertex descriptor in a MD5 file */ struct VertexDesc { VertexDesc() AI_NO_EXCEPT - : mFirstWeight(0) - , mNumWeights(0) { + : mFirstWeight(0), mNumWeights(0) { // empty } @@ -210,13 +201,12 @@ struct VertexDesc { unsigned int mNumWeights; }; -typedef std::vector< VertexDesc > VertexList; +using VertexArray = std::vector; // --------------------------------------------------------------------------- /** Represents a vertex weight descriptor in a MD5 file */ -struct WeightDesc -{ +struct WeightDesc { //! Index of the bone to which this weight refers unsigned int mBone; @@ -228,28 +218,27 @@ struct WeightDesc aiVector3D vOffsetPosition; }; -typedef std::vector< WeightDesc > WeightList; -typedef std::vector< aiFace > FaceList; +using WeightArray = std::vector; +using FaceArray = std::vector; // --------------------------------------------------------------------------- /** Represents a mesh in a MD5 file */ -struct MeshDesc -{ +struct MeshDesc { //! Weights of the mesh - WeightList mWeights; + WeightArray mWeights; //! Vertices of the mesh - VertexList mVertices; + VertexArray mVertices; //! Faces of the mesh - FaceList mFaces; + FaceArray mFaces; //! Name of the shader (=texture) to be assigned to the mesh aiString mShader; }; -typedef std::vector< MeshDesc > MeshList; +using MeshArray = std::vector; // --------------------------------------------------------------------------- // Convert a quaternion to its usual representation @@ -261,9 +250,11 @@ inline void ConvertQuaternion (const aiVector3D& in, aiQuaternion& out) { const float t = 1.0f - (in.x*in.x) - (in.y*in.y) - (in.z*in.z); - if (t < 0.0f) + if (t < 0.0f) { out.w = 0.0f; - else out.w = std::sqrt (t); + } else { + out.w = std::sqrt (t); + } // Assimp convention. out.w *= -1.f; @@ -272,23 +263,21 @@ inline void ConvertQuaternion (const aiVector3D& in, aiQuaternion& out) { // --------------------------------------------------------------------------- /** Parses the data sections of a MD5 mesh file */ -class MD5MeshParser -{ +class MD5MeshParser { public: - // ------------------------------------------------------------------- /** Constructs a new MD5MeshParser instance from an existing * preparsed list of file sections. * * @param mSections List of file sections (output of MD5Parser) */ - explicit MD5MeshParser(SectionList& mSections); + explicit MD5MeshParser(SectionArray& mSections); //! List of all meshes - MeshList mMeshes; + MeshArray mMeshes; //! List of all joints - BoneList mJoints; + BoneArray mJoints; }; // remove this flag if you need to the bounding box data @@ -297,30 +286,28 @@ public: // --------------------------------------------------------------------------- /** Parses the data sections of a MD5 animation file */ -class MD5AnimParser -{ +class MD5AnimParser { public: - // ------------------------------------------------------------------- /** Constructs a new MD5AnimParser instance from an existing * preparsed list of file sections. * * @param mSections List of file sections (output of MD5Parser) */ - explicit MD5AnimParser(SectionList& mSections); + explicit MD5AnimParser(SectionArray& mSections); //! Output frame rate float fFrameRate; //! List of animation bones - AnimBoneList mAnimatedBones; + AnimBoneArray mAnimatedBones; //! List of base frames - BaseFrameList mBaseFrames; + BaseFrameArray mBaseFrames; //! List of animation frames - FrameList mFrames; + FrameArray mFrames; //! Number of animated components unsigned int mNumAnimatedComponents; @@ -329,18 +316,15 @@ public: // --------------------------------------------------------------------------- /** Parses the data sections of a MD5 camera animation file */ -class MD5CameraParser -{ +class MD5CameraParser { public: - // ------------------------------------------------------------------- /** Constructs a new MD5CameraParser instance from an existing * preparsed list of file sections. * * @param mSections List of file sections (output of MD5Parser) */ - explicit MD5CameraParser(SectionList& mSections); - + explicit MD5CameraParser(SectionArray& mSections); //! Output frame rate float fFrameRate; @@ -349,17 +333,15 @@ public: std::vector cuts; //! Frames - CameraFrameList frames; + CameraFrameArray frames; }; // --------------------------------------------------------------------------- /** Parses the block structure of MD5MESH and MD5ANIM files (but does no * further processing) */ -class MD5Parser -{ +class MD5Parser { public: - // ------------------------------------------------------------------- /** Constructs a new MD5Parser instance from an existing buffer. * @@ -368,100 +350,112 @@ public: */ MD5Parser(char* buffer, unsigned int fileSize); - // ------------------------------------------------------------------- /** Report a specific error message and throw an exception * @param error Error message to be reported * @param line Index of the line where the error occurred */ - AI_WONT_RETURN static void ReportError (const char* error, unsigned int line) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN static void ReportError(const char* error, unsigned int line) AI_WONT_RETURN_SUFFIX; // ------------------------------------------------------------------- /** Report a specific warning * @param warn Warn message to be reported * @param line Index of the line where the error occurred */ - static void ReportWarning (const char* warn, unsigned int line); + static void ReportWarning(const char* warn, unsigned int line); + // ------------------------------------------------------------------- + /** Report a specific error + * @param error Error message to be reported + */ + AI_WONT_RETURN void ReportError (const char* error) AI_WONT_RETURN_SUFFIX; - void ReportError (const char* error) { - return ReportError(error, lineNumber); - } - - void ReportWarning (const char* warn) { - return ReportWarning(warn, lineNumber); - } - -public: + // ------------------------------------------------------------------- + /** Report a specific warning + * @param error Warn message to be reported + */ + void ReportWarning (const char* warn); //! List of all sections which have been read - SectionList mSections; + SectionArray mSections; private: - - // ------------------------------------------------------------------- - /** Parses a file section. The current file pointer must be outside - * of a section. - * @param out Receives the section data - * @return true if the end of the file has been reached - * @throws ImportErrorException if an error occurs - */ bool ParseSection(Section& out); - - // ------------------------------------------------------------------- - /** Parses the file header - * @throws ImportErrorException if an error occurs - */ void ParseHeader(); + bool SkipLine(const char* in, const char** out); + bool SkipLine( ); + bool SkipSpacesAndLineEnd( const char* in, const char** out); + bool SkipSpacesAndLineEnd(); + bool SkipSpaces(); - - // override these functions to make sure the line counter gets incremented - // ------------------------------------------------------------------- - bool SkipLine( const char* in, const char** out) - { - ++lineNumber; - return Assimp::SkipLine(in,out); - } - // ------------------------------------------------------------------- - bool SkipLine( ) - { - return SkipLine(buffer,(const char**)&buffer); - } - // ------------------------------------------------------------------- - bool SkipSpacesAndLineEnd( const char* in, const char** out) - { - bool bHad = false; - bool running = true; - while (running) { - if( *in == '\r' || *in == '\n') { - // we open files in binary mode, so there could be \r\n sequences ... - if (!bHad) { - bHad = true; - ++lineNumber; - } - } - else if (*in == '\t' || *in == ' ')bHad = false; - else break; - in++; - } - *out = in; - return *in != '\0'; - } - // ------------------------------------------------------------------- - bool SkipSpacesAndLineEnd( ) - { - return SkipSpacesAndLineEnd(buffer,(const char**)&buffer); - } - // ------------------------------------------------------------------- - bool SkipSpaces( ) - { - return Assimp::SkipSpaces((const char**)&buffer); - } - +private: char* buffer; + const char* bufferEnd; unsigned int fileSize; unsigned int lineNumber; }; -}} + +// ------------------------------------------------------------------- +inline void MD5Parser::ReportWarning (const char* warn) { + return ReportWarning(warn, lineNumber); +} + +// ------------------------------------------------------------------- +inline void MD5Parser::ReportError(const char* error) { + ReportError(error, lineNumber); +} + +// ------------------------------------------------------------------- +inline bool MD5Parser::SkipLine(const char* in, const char** out) { + ++lineNumber; + return Assimp::SkipLine(in, out, bufferEnd); +} + +// ------------------------------------------------------------------- +inline bool MD5Parser::SkipLine( ) { + return SkipLine(buffer,(const char**)&buffer); +} + +// ------------------------------------------------------------------- +inline bool MD5Parser::SkipSpacesAndLineEnd( const char* in, const char** out) { + if (in == bufferEnd) { + *out = in; + return false; + } + + bool bHad = false, running = true; + while (running) { + if( *in == '\r' || *in == '\n') { + // we open files in binary mode, so there could be \r\n sequences ... + if (!bHad) { + bHad = true; + ++lineNumber; + } + } else if (*in == '\t' || *in == ' ') { + bHad = false; + } else { + break; + } + ++in; + if (in == bufferEnd) { + break; + } + } + *out = in; + return *in != '\0'; +} + +// ------------------------------------------------------------------- +inline bool MD5Parser::SkipSpacesAndLineEnd() { + return SkipSpacesAndLineEnd(buffer,(const char**)&buffer); +} + +// ------------------------------------------------------------------- +inline bool MD5Parser::SkipSpaces() { + return Assimp::SkipSpaces((const char**)&buffer, bufferEnd); +} + +} // namespace Assimp +} // namespace MD5 #endif // AI_MD5PARSER_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/MDC/MDCFileData.h b/Engine/lib/assimp/code/AssetLib/MDC/MDCFileData.h index 36c589e91..4c5b4127c 100644 --- a/Engine/lib/assimp/code/AssetLib/MDC/MDCFileData.h +++ b/Engine/lib/assimp/code/AssetLib/MDC/MDCFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -61,7 +61,7 @@ http://themdcfile.planetwolfenstein.gamespy.com/MDC_File_Format.pdf namespace Assimp { namespace MDC { -// to make it easier for us, we test the magic word against both "endianesses" +// to make it easier for us, we test the magic word against both "endiannesses" #define AI_MDC_MAGIC_NUMBER_BE AI_MAKE_MAGIC("CPDI") #define AI_MDC_MAGIC_NUMBER_LE AI_MAKE_MAGIC("IDPC") diff --git a/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.cpp b/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.cpp index b107516de..c81ba67a6 100644 --- a/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,7 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; using namespace Assimp::MDC; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Return To Castle Wolfenstein Mesh Importer", "", "", @@ -103,10 +103,6 @@ MDCImporter::MDCImporter() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MDCImporter::~MDCImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MDCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -199,7 +195,7 @@ void MDCImporter::InternReadFile( std::unique_ptr file(pIOHandler->Open(pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open MDC file ", pFile, "."); } diff --git a/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.h b/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.h index 6e67cd12f..09ecc6865 100644 --- a/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.h +++ b/Engine/lib/assimp/code/AssetLib/MDC/MDCLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,7 +62,7 @@ using namespace MDC; class MDCImporter : public BaseImporter { public: MDCImporter(); - ~MDCImporter() override; + ~MDCImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h index 28b1b2822..485f538ab 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1FileData.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h index d412aeede..344574d24 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportDefinitions.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h index 340ba2da5..7c2fa2ff2 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1ImportSettings.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,7 +63,8 @@ struct HL1ImportSettings { read_bone_controllers(false), read_hitboxes(false), read_textures(false), - read_misc_global_info(false) { + read_misc_global_info(false), + transform_coord_system(true) { } bool read_animations; @@ -76,6 +77,7 @@ struct HL1ImportSettings { bool read_hitboxes; bool read_textures; bool read_misc_global_info; + bool transform_coord_system; }; } // namespace HalfLife diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index 93d37536c..2d7f6a7c2 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -470,14 +470,16 @@ void HL1MDLLoader::read_bones() { temp_bones_.resize(header_->numbones); + // Create the main 'bones' node that will contain all MDL root bones. aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES); rootnode_children_.push_back(bones_node); - bones_node->mNumChildren = static_cast(header_->numbones); - bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; + + // Store roots bones IDs temporarily. + std::vector roots; // Create bone matrices in local space. for (int i = 0; i < header_->numbones; ++i) { - aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]); + aiNode *bone_node = temp_bones_[i].node = new aiNode(unique_bones_names[i]); aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]); temp_bones_[i].absolute_transform = bone_node->mTransformation = @@ -485,9 +487,11 @@ void HL1MDLLoader::read_bones() { aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2])); if (pbone[i].parent == -1) { - bone_node->mParent = scene_->mRootNode; + bone_node->mParent = bones_node; + roots.push_back(i); // This bone has no parent. Add it to the roots list. } else { - bone_node->mParent = bones_node->mChildren[pbone[i].parent]; + bone_node->mParent = temp_bones_[pbone[i].parent].node; + temp_bones_[pbone[i].parent].children.push_back(i); // Add this bone to the parent bone's children list. temp_bones_[i].absolute_transform = temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation; @@ -496,6 +500,36 @@ void HL1MDLLoader::read_bones() { temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform; temp_bones_[i].offset_matrix.Inverse(); } + + // Allocate memory for each MDL root bone. + bones_node->mNumChildren = static_cast(roots.size()); + bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; + + // Build all bones children hierarchy starting from each MDL root bone. + for (size_t i = 0; i < roots.size(); ++i) + { + const TempBone &root_bone = temp_bones_[roots[i]]; + bones_node->mChildren[i] = root_bone.node; + build_bone_children_hierarchy(root_bone); + } +} + +void HL1MDLLoader::build_bone_children_hierarchy(const TempBone &bone) +{ + if (bone.children.empty()) + return; + + aiNode* bone_node = bone.node; + bone_node->mNumChildren = static_cast(bone.children.size()); + bone_node->mChildren = new aiNode *[bone_node->mNumChildren]; + + // Build each child bone's hierarchy recursively. + for (size_t i = 0; i < bone.children.size(); ++i) + { + const TempBone &child_bone = temp_bones_[bone.children[i]]; + bone_node->mChildren[i] = child_bone.node; + build_bone_children_hierarchy(child_bone); + } } // ------------------------------------------------------------------------------------------------ diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h index d87d6d3e8..ab5bb6942 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -143,6 +143,14 @@ private: */ static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers); + /** + * \brief Build a bone's node children hierarchy. + * + * \param[in] bone The bone for which we must build all children hierarchy. + */ + struct TempBone; + void build_bone_children_hierarchy(const TempBone& bone); + /** Output scene to be filled */ aiScene *scene_; @@ -198,11 +206,13 @@ private: TempBone() : node(nullptr), absolute_transform(), - offset_matrix() {} + offset_matrix(), + children() {} aiNode *node; aiMatrix4x4 absolute_transform; aiMatrix4x4 offset_matrix; + std::vector children; // Bone children }; std::vector temp_bones_; @@ -222,7 +232,7 @@ void HL1MDLLoader::load_file_into_buffer(const std::string &file_path, unsigned std::unique_ptr file(io_->Open(file_path)); - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open MDL file ", DefaultIOSystem::fileName(file_path), "."); } diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h index 4ef8a13ce..9c389a659 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HL1MeshTrivert.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h index c7808c401..25ef27bca 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/HalfLifeMDLBaseHeader.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h index 003774dc1..c8cdf9b15 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/LogFunctions.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp index 3cca8c558..b79ec3613 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h index 73b6f9e74..cf190bc3b 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/HalfLife/UniqueNameGenerator.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h b/Engine/lib/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h index 2eecac2dc..cbdc977ad 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/MDLDefaultColorMap.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MDL/MDLFileData.h b/Engine/lib/assimp/code/AssetLib/MDL/MDLFileData.h index 7ec2afe9a..2aff59856 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/MDLFileData.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/MDLFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -67,7 +67,7 @@ namespace Assimp { namespace MDL { // ------------------------------------------------------------------------------------- -// to make it easier for us, we test the magic word against both "endianesses" +// to make it easier for us, we test the magic word against both "endiannesses" // magic bytes used in Quake 1 MDL meshes #define AI_MDL_MAGIC_NUMBER_BE AI_MAKE_MAGIC("IDPO") diff --git a/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.cpp b/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.cpp index b2bd2d2f1..40725b1f7 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -65,7 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Quake Mesh / 3D GameStudio Mesh Importer", "", "", @@ -96,14 +96,10 @@ MDLImporter::MDLImporter() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MDLImporter::~MDLImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { - static const uint32_t tokens[] = { + static constexpr uint32_t tokens[] = { AI_MDL_MAGIC_NUMBER_LE_HL2a, AI_MDL_MAGIC_NUMBER_LE_HL2b, AI_MDL_MAGIC_NUMBER_LE_GS7, @@ -142,6 +138,7 @@ void MDLImporter::SetupProperties(const Importer *pImp) { mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true); mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true); mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true); + mHL1ImportSettings.transform_coord_system = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_TRANSFORM_COORD_SYSTEM); } // ------------------------------------------------------------------------------------------------ @@ -150,6 +147,20 @@ const aiImporterDesc *MDLImporter::GetInfo() const { return &desc; } +// ------------------------------------------------------------------------------------------------ +static void transformCoordinateSystem(const aiScene *pScene) { + if (pScene == nullptr) { + return; + } + + pScene->mRootNode->mTransformation = aiMatrix4x4( + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f + ); +} + // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void MDLImporter::InternReadFile(const std::string &pFile, @@ -159,7 +170,7 @@ void MDLImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(pIOHandler->Open(pFile)); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open MDL file ", pFile, "."); } @@ -250,18 +261,16 @@ void MDLImporter::InternReadFile(const std::string &pFile, ". Magic word (", ai_str_toprintable((const char *)&iMagicWord, sizeof(iMagicWord)), ") is not known"); } - if (is_half_life){ + if (is_half_life && mHL1ImportSettings.transform_coord_system) { // Now rotate the whole scene 90 degrees around the z and x axes to convert to internal coordinate system - pScene->mRootNode->mTransformation = aiMatrix4x4( - 0.f, -1.f, 0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, - -1.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 1.f); - } - else { + transformCoordinateSystem(pScene); + } else { // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system - pScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + pScene->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f); } DeleteBufferAndCleanup(); @@ -271,10 +280,16 @@ void MDLImporter::InternReadFile(const std::string &pFile, } } +// ------------------------------------------------------------------------------------------------ +// Check whether we're still inside the valid file range +bool MDLImporter::IsPosValid(const void *szPos) const { + return szPos && (const unsigned char *)szPos <= this->mBuffer + this->iFileSize && szPos >= this->mBuffer; +} + // ------------------------------------------------------------------------------------------------ // Check whether we're still inside the valid file range void MDLImporter::SizeCheck(const void *szPos) { - if (!szPos || (const unsigned char *)szPos > this->mBuffer + this->iFileSize) { + if (!IsPosValid(szPos)) { throw DeadlyImportError("Invalid MDL file. The file is too small " "or contains invalid data."); } @@ -284,7 +299,7 @@ void MDLImporter::SizeCheck(const void *szPos) { // Just for debugging purposes void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) { ai_assert(nullptr != szFile); - if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) { + if (!IsPosValid(szPos)) { // remove a directory if there is one const char *szFilePtr = ::strrchr(szFile, '\\'); if (!szFilePtr) { @@ -298,7 +313,7 @@ void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int } char szBuffer[1024]; - ::sprintf(szBuffer, "Invalid MDL file. The file is too small " + ::snprintf(szBuffer, sizeof(szBuffer), "Invalid MDL file. The file is too small " "or contains invalid data (File: %s Line: %u)", szFilePtr, iLine); @@ -404,8 +419,15 @@ void MDLImporter::InternReadFile_Quake1() { this->CreateTextureARGB8_3DGS_MDL3(szCurrent + iNumImages * sizeof(float)); } // go to the end of the skin section / the beginning of the next skin - szCurrent += pcHeader->skinheight * pcHeader->skinwidth + - sizeof(float) * iNumImages; + bool overflow = false; + if (pcHeader->skinwidth != 0 || pcHeader->skinheight != 0) { + if ((pcHeader->skinheight > INT_MAX / pcHeader->skinwidth) || (pcHeader->skinwidth > INT_MAX / pcHeader->skinheight)){ + overflow = true; + } + if (!overflow) { + szCurrent += pcHeader->skinheight * pcHeader->skinwidth +sizeof(float) * iNumImages; + } + } } } else { szCurrent += sizeof(uint32_t); @@ -953,7 +975,7 @@ void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) { // no real name for our poor bone is specified :-( - pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN, + pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, AI_MAXLEN, "UnnamedBone_%i", iBone); } else { // Make sure we won't run over the buffer's end if there is no @@ -968,7 +990,7 @@ void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) } // store the name of the bone - pcOutBone->mName.length = (size_t)iMaxLen; + pcOutBone->mName.length = static_cast(iMaxLen); ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length); pcOutBone->mName.data[pcOutBone->mName.length] = '\0'; } @@ -1558,7 +1580,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7() { } else { pcNode->mName.length = (ai_uint32)::strlen(szBuffer); } - ::strncpy(pcNode->mName.data, szBuffer, MAXLEN - 1); + ::strncpy(pcNode->mName.data, szBuffer, AI_MAXLEN - 1); ++p; } } diff --git a/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.h b/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.h index b7d87e88d..f99f061ce 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.h +++ b/Engine/lib/assimp/code/AssetLib/MDL/MDLLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -39,10 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - -/** @file MDLLoader.h - * @brief Declaration of the loader for MDL files - */ +/// @file MDLLoader.h +/// @brief Declaration of the loader for MDL files #pragma once #ifndef AI_MDLLOADER_H_INCLUDED #define AI_MDLLOADER_H_INCLUDED @@ -83,11 +81,10 @@ using namespace MDL; * them all with a single 1000-line function-beast. However, it has been * split into several code paths to make the code easier to read and maintain. */ -class MDLImporter : public BaseImporter -{ +class MDLImporter : public BaseImporter { public: MDLImporter(); - ~MDLImporter() override; + ~MDLImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -139,7 +136,7 @@ protected: // ------------------------------------------------------------------- /** Import a CS:S/HL2 MDL file (not fully implemented) */ - void InternReadFile_HL2( ); + AI_WONT_RETURN void InternReadFile_HL2( ) AI_WONT_RETURN_SUFFIX; // ------------------------------------------------------------------- /** Check whether a given position is inside the valid range @@ -150,6 +147,7 @@ protected: */ void SizeCheck(const void* szPos); void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine); + bool IsPosValid(const void* szPos) const; // ------------------------------------------------------------------- /** Validate the header data structure of a game studio MDL7 file diff --git a/Engine/lib/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp b/Engine/lib/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp index db4b534f2..d0a2d5f79 100644 --- a/Engine/lib/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MDL/MDLMaterialLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -123,9 +123,8 @@ aiColor4D MDLImporter::ReplaceTextureWithColor(const aiTexture *pcTexture) { // Read a texture from a MDL3 file void MDLImporter::CreateTextureARGB8_3DGS_MDL3(const unsigned char *szData) { const MDL::Header *pcHeader = (const MDL::Header *)mBuffer; //the endianness is already corrected in the InternReadFile_3DGS_MDL345 function - - VALIDATE_FILE_SIZE(szData + pcHeader->skinwidth * - pcHeader->skinheight); + const size_t len = pcHeader->skinwidth * pcHeader->skinheight; + VALIDATE_FILE_SIZE(szData + len); // allocate a new texture object aiTexture *pcNew = new aiTexture(); @@ -470,7 +469,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( ASSIMP_LOG_ERROR("Found a reference to an embedded DDS texture, but texture width is zero, aborting import."); return; } - + pcNew.reset(new aiTexture); pcNew->mHeight = 0; pcNew->mWidth = iWidth; @@ -481,6 +480,8 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( pcNew->achFormatHint[2] = 's'; pcNew->achFormatHint[3] = '\0'; + SizeCheck(szCurrent + pcNew->mWidth); + pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth]; memcpy(pcNew->pcData, szCurrent, pcNew->mWidth); szCurrent += iWidth; @@ -493,12 +494,12 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( aiString szFile; const size_t iLen = strlen((const char *)szCurrent); - size_t iLen2 = iLen + 1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + size_t iLen2 = iLen > (AI_MAXLEN - 1) ? (AI_MAXLEN - 1) : iLen; memcpy(szFile.data, (const char *)szCurrent, iLen2); + szFile.data[iLen2] = '\0'; szFile.length = static_cast(iLen2); - szCurrent += iLen2; + szCurrent += iLen2 + 1; // place this as diffuse texture pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0)); @@ -609,7 +610,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( if (is_not_qnan(clrTexture.r)) { clrTemp.r *= clrTexture.a; } - pcMatOut->AddProperty(&clrTemp.r, 1, AI_MATKEY_OPACITY); + pcMatOut->AddProperty(&clrTemp.r, 1, AI_MATKEY_OPACITY); // read phong power int iShadingMode = (int)aiShadingMode_Gouraud; @@ -703,7 +704,14 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7( tex.pcData = bad_texel; tex.mHeight = iHeight; tex.mWidth = iWidth; - ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex); + + try { + ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex); + } catch (...) { + // FIX: Important, otherwise the destructor will crash + tex.pcData = nullptr; + throw; + } // FIX: Important, otherwise the destructor will crash tex.pcData = nullptr; @@ -722,9 +730,12 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7( // if an ASCII effect description (HLSL?) is contained in the file, // we can simply ignore it ... if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) { - int32_t iMe = *((int32_t *)szCurrent); + VALIDATE_FILE_SIZE(szCurrent + sizeof(int32_t)); + int32_t iMe = 0; + ::memcpy(&iMe, szCurrent, sizeof(int32_t)); AI_SWAP4(iMe); szCurrent += sizeof(char) * iMe + sizeof(int32_t); + VALIDATE_FILE_SIZE(szCurrent); } *szCurrentOut = szCurrent; } diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDCpp14.h b/Engine/lib/assimp/code/AssetLib/MMD/MMDCpp14.h index 10d376829..4a04bf69e 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDCpp14.h +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDCpp14.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.cpp b/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.cpp index 97b04f4eb..1e88cefd2 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,7 +57,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -static const aiImporterDesc desc = { "MMD Importer", +static constexpr aiImporterDesc desc = { "MMD Importer", "", "", "surfaces supported?", @@ -81,10 +81,6 @@ MMDImporter::MMDImporter() : m_strAbsPath = io.getOsSeparator(); } -// ------------------------------------------------------------------------------------------------ -// Destructor. -MMDImporter::~MMDImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns true, if file is an pmx file. bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, @@ -269,7 +265,7 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, dynamic_cast(v->skinning.get()); switch (v->skinning_type) { case pmx::PmxVertexSkinningType::BDEF1: - bone_vertex_map[vsBDEF1_ptr->bone_index].emplace_back(index, 1.0); + bone_vertex_map[vsBDEF1_ptr->bone_index].emplace_back(index, static_cast(1)); break; case pmx::PmxVertexSkinningType::BDEF2: bone_vertex_map[vsBDEF2_ptr->bone_index1].emplace_back(index, vsBDEF2_ptr->bone_weight); diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.h b/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.h index 36f384829..71fc534a4 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.h +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -50,46 +50,34 @@ struct aiMesh; namespace Assimp { // ------------------------------------------------------------------------------------------------ -/// \class MMDImporter -/// \brief Imports MMD a pmx/pmd/vmd file +/// @class MMDImporter +/// @brief Imports MMD a pmx/pmd/vmd file // ------------------------------------------------------------------------------------------------ class MMDImporter : public BaseImporter { public: - /// \brief Default constructor + /// @brief Default constructor MMDImporter(); - /// \brief Destructor - ~MMDImporter() override; + /// @brief Destructor + ~MMDImporter() override = default; public: - /// \brief Returns whether the class can handle the format of the given file. - /// \remark See BaseImporter::CanRead() for details. + /// @brief Returns whether the class can handle the format of the given file. + /// @remark See BaseImporter::CanRead() for details. bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; private: - //! \brief Appends the supported extension. const aiImporterDesc* GetInfo() const override; - - //! \brief File import implementation. void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; - - //! \brief Create the data from imported content. void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene); - - //! \brief Create the mesh aiMesh* CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount); - - //! \brief Create the material aiMaterial* CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel); private: - //! Data buffer std::vector m_Buffer; - //! Absolute pathname of model in file system std::string m_strAbsPath; }; -// ------------------------------------------------------------------------------------------------ } // Namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmdParser.h b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmdParser.h index 23aaac555..b11c72f8d 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmdParser.h +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmdParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.cpp b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.cpp index ca37ba199..5a3e61dcd 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -42,11 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "MMDPmxParser.h" #include -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include "../contrib/utf8cpp/source/utf8.h" -#endif +#include "utf8.h" #include namespace pmx @@ -93,7 +89,7 @@ namespace pmx { return std::string(); } - buffer.reserve(size); + buffer.resize(size); stream->read((char*) buffer.data(), size); if (encoding == 0) { diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.h b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.h index f2e387975..e90e554e7 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.h +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDPmxParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "MMDCpp14.h" namespace pmx @@ -730,7 +731,7 @@ namespace pmx std::unique_ptr anchers; int pin_vertex_count; std::unique_ptr pin_vertices; - void Read(std::istream *stream, PmxSetting *setting); + AI_WONT_RETURN void Read(std::istream *stream, PmxSetting *setting) AI_WONT_RETURN_SUFFIX; }; class PmxModel diff --git a/Engine/lib/assimp/code/AssetLib/MMD/MMDVmdParser.h b/Engine/lib/assimp/code/AssetLib/MMD/MMDVmdParser.h index d5c7dedff..2ba9fb931 100644 --- a/Engine/lib/assimp/code/AssetLib/MMD/MMDVmdParser.h +++ b/Engine/lib/assimp/code/AssetLib/MMD/MMDVmdParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.cpp b/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.cpp index 577078158..fac102f5f 100644 --- a/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -60,7 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Milkshape 3D Importer", "", "", @@ -84,9 +84,6 @@ MS3DImporter::MS3DImporter() : mScene() {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MS3DImporter::~MS3DImporter() = default; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool MS3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const @@ -486,7 +483,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile, for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) { aiFace& f = m->mFaces[j]; - if (g.triangles[j]>triangles.size()) { + if (g.triangles[j] >= triangles.size()) { throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed"); } @@ -494,7 +491,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile, f.mIndices = new unsigned int[f.mNumIndices=3]; for (unsigned int k = 0; k < 3; ++k,++n) { - if (t.indices[k]>vertices.size()) { + if (t.indices[k] >= vertices.size()) { throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed"); } diff --git a/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.h b/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.h index 4bd417566..5a28391eb 100644 --- a/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.h +++ b/Engine/lib/assimp/code/AssetLib/MS3D/MS3DLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include + struct aiNode; namespace Assimp { @@ -58,7 +59,7 @@ namespace Assimp { class MS3DImporter : public BaseImporter { public: MS3DImporter(); - ~MS3DImporter() override; + ~MS3DImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.cpp b/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.cpp index 1fac224b8..3515c14b4 100644 --- a/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,8 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Implementation of the NDO importer class. */ - #ifndef ASSIMP_BUILD_NO_NDO_IMPORTER + #include "NDOLoader.h" #include #include @@ -52,10 +52,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include using namespace Assimp; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Nendo Mesh Importer", "", "", @@ -68,14 +69,6 @@ static const aiImporterDesc desc = { "ndo" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -NDOImporter::NDOImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -NDOImporter::~NDOImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool NDOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const @@ -160,6 +153,9 @@ void NDOImporter::InternReadFile( const std::string& pFile, temp = file_format >= 12 ? reader.GetU4() : reader.GetU2(); head = (const char*)reader.GetPtr(); + if (std::numeric_limits::max() - 76 < temp) { + throw DeadlyImportError("Invalid name length"); + } reader.IncPtr(temp + 76); /* skip unknown stuff */ obj.name = std::string(head, temp); @@ -268,7 +264,7 @@ void NDOImporter::InternReadFile( const std::string& pFile, const unsigned int key = v.first; unsigned int cur_edge = v.second; - while (1) { + while (true) { unsigned int next_edge, next_vert; if (key == obj.edges[cur_edge].edge[3]) { next_edge = obj.edges[cur_edge].edge[5]; diff --git a/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.h b/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.h index 61dd93981..c3ab15230 100644 --- a/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.h +++ b/Engine/lib/assimp/code/AssetLib/NDO/NDOLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, @@ -65,8 +65,8 @@ class Importer; */ class NDOImporter : public BaseImporter { public: - NDOImporter(); - ~NDOImporter() override; + NDOImporter() = default; + ~NDOImporter() override = default; //! Represents a single edge struct Edge { diff --git a/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.cpp b/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.cpp index 953b0d48e..62f97d832 100644 --- a/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,9 +56,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Neutral File Format Importer", "", "", @@ -71,14 +71,6 @@ static const aiImporterDesc desc = { "enff nff" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -NFFImporter::NFFImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -NFFImporter::~NFFImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool NFFImporter::CanRead(const std::string & pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { @@ -93,8 +85,8 @@ const aiImporterDesc *NFFImporter::GetInfo() const { // ------------------------------------------------------------------------------------------------ #define AI_NFF_PARSE_FLOAT(f) \ - SkipSpaces(&sz); \ - if (!::IsLineEnd(*sz)) sz = fast_atoreal_move(sz, (ai_real &)f); + SkipSpaces(&sz, lineEnd); \ + if (!IsLineEnd(*sz)) sz = fast_atoreal_move(sz, (ai_real &)f); // ------------------------------------------------------------------------------------------------ #define AI_NFF_PARSE_TRIPLE(v) \ @@ -119,7 +111,7 @@ const aiImporterDesc *NFFImporter::GetInfo() const { ASSIMP_LOG_WARN("NFF2: Unexpected EOF, can't read next token"); \ break; \ } \ - SkipSpaces(line, &sz); \ + SkipSpaces(line, &sz, lineEnd); \ } while (IsLineEnd(*sz)) // ------------------------------------------------------------------------------------------------ @@ -129,7 +121,7 @@ void NFFImporter::LoadNFF2MaterialTable(std::vector &output, std::unique_ptr file(pIOHandler->Open(path, "rb")); // Check whether we can read from the file - if (!file.get()) { + if (!file) { ASSIMP_LOG_ERROR("NFF2: Unable to open material library ", path, "."); return; } @@ -156,9 +148,9 @@ void NFFImporter::LoadNFF2MaterialTable(std::vector &output, // No read the file line per line char line[4096]; - const char *sz; + const char *sz, *lineEnd = &line[2095]+1; while (GetNextLine(buffer, line)) { - SkipSpaces(line, &sz); + SkipSpaces(line, &sz, lineEnd); // 'version' defines the version of the file format if (TokenMatch(sz, "version", 7)) { @@ -206,18 +198,16 @@ void NFFImporter::LoadNFF2MaterialTable(std::vector &output, // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void NFFImporter::InternReadFile(const std::string &pFile, - aiScene *pScene, IOSystem *pIOHandler) { - std::unique_ptr file(pIOHandler->Open(pFile, "rb")); - - // Check whether we can read from the file - if (!file.get()) - throw DeadlyImportError("Failed to open NFF file ", pFile, "."); +void NFFImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr stream(pIOHandler->Open(file, "rb")); + if (!stream) { + throw DeadlyImportError("Failed to open NFF file ", file, "."); + } // allocate storage and copy the contents of the file to a memory buffer // (terminate it with zero) std::vector mBuffer2; - TextFileToBuffer(file.get(), mBuffer2); + TextFileToBuffer(stream.get(), mBuffer2); const char *buffer = &mBuffer2[0]; // mesh arrays - separate here to make the handling of the pointers below easier. @@ -227,8 +217,10 @@ void NFFImporter::InternReadFile(const std::string &pFile, std::vector meshesLocked; char line[4096]; + const char *lineEnd = &line[4096]; const char *sz; + // camera parameters aiVector3D camPos, camUp(0.f, 1.f, 0.f), camLookAt(0.f, 0.f, 1.f); ai_real angle = 45.f; @@ -273,7 +265,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, CommentRemover::RemoveLineComments("//", &mBuffer2[0]); while (GetNextLine(buffer, line)) { - SkipSpaces(line, &sz); + SkipSpaces(line, &sz, lineEnd); if (TokenMatch(sz, "version", 7)) { ASSIMP_LOG_INFO("NFF (Sense8) file format: ", sz); } else if (TokenMatch(sz, "viewpos", 7)) { @@ -303,7 +295,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, // material table - an external file if (TokenMatch(sz, "mtable", 6)) { - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); sz3 = sz; while (!IsSpaceOrNewLine(*sz)) ++sz; @@ -324,12 +316,12 @@ void NFFImporter::InternReadFile(const std::string &pFile, std::string::size_type sepPos; if ((std::string::npos == (sepPos = path.find_last_of('\\')) || !sepPos) && (std::string::npos == (sepPos = path.find_last_of('/')) || !sepPos)) { - sepPos = pFile.find_last_of('\\'); + sepPos = file.find_last_of('\\'); if (std::string::npos == sepPos) { - sepPos = pFile.find_last_of('/'); + sepPos = file.find_last_of('/'); } if (std::string::npos != sepPos) { - path = pFile.substr(0, sepPos + 1) + path; + path = file.substr(0, sepPos + 1) + path; } } LoadNFF2MaterialTable(materialTable, path, pIOHandler); @@ -338,8 +330,8 @@ void NFFImporter::InternReadFile(const std::string &pFile, break; } - // read the numbr of vertices - unsigned int num = ::strtoul10(sz, &sz); + // read the number of vertices + unsigned int num = strtoul10(sz, &sz); // temporary storage std::vector tempColors; @@ -359,13 +351,13 @@ void NFFImporter::InternReadFile(const std::string &pFile, // parse all other attributes in the line while (true) { - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); if (IsLineEnd(*sz)) break; // color definition if (TokenMatch(sz, "0x", 2)) { hasColor = true; - unsigned int numIdx = ::strtoul16(sz, &sz); + unsigned int numIdx = strtoul16(sz, &sz); aiColor4D clr; clr.a = 1.f; @@ -403,30 +395,28 @@ void NFFImporter::InternReadFile(const std::string &pFile, } AI_NFF2_GET_NEXT_TOKEN(); - if (!num) throw DeadlyImportError("NFF2: There are zero vertices"); - num = ::strtoul10(sz, &sz); + if (!num) + throw DeadlyImportError("NFF2: There are zero vertices"); + num = strtoul10(sz, &sz); std::vector tempIdx; tempIdx.reserve(10); for (unsigned int i = 0; i < num; ++i) { AI_NFF2_GET_NEXT_TOKEN(); - SkipSpaces(line, &sz); - unsigned int numIdx = ::strtoul10(sz, &sz); + SkipSpaces(line, &sz, lineEnd); + unsigned int numIdx = strtoul10(sz, &sz); // read all faces indices if (numIdx) { - // mesh.faces.push_back(numIdx); - // tempIdx.erase(tempIdx.begin(),tempIdx.end()); tempIdx.resize(numIdx); for (unsigned int a = 0; a < numIdx; ++a) { - SkipSpaces(sz, &sz); - unsigned int m = ::strtoul10(sz, &sz); + SkipSpaces(sz, &sz, lineEnd); + unsigned int m = strtoul10(sz, &sz); if (m >= (unsigned int)tempPositions.size()) { ASSIMP_LOG_ERROR("NFF2: Vertex index overflow"); m = 0; } - // mesh.vertices.push_back (tempPositions[idx]); tempIdx[a] = m; } } @@ -439,14 +429,14 @@ void NFFImporter::InternReadFile(const std::string &pFile, shader.color = aiColor3D(1.f, 1.f, 1.f); aiColor4D c = aiColor4D(1.f, 1.f, 1.f, 1.f); while (true) { - SkipSpaces(sz, &sz); + SkipSpaces(sz, &sz, lineEnd); if (IsLineEnd(*sz)) break; // per-polygon colors if (TokenMatch(sz, "0x", 2)) { hasColor = true; const char *sz2 = sz; - numIdx = ::strtoul16(sz, &sz); + numIdx = strtoul16(sz, &sz); const unsigned int diff = (unsigned int)(sz - sz2); // 0xRRGGBB @@ -517,8 +507,8 @@ void NFFImporter::InternReadFile(const std::string &pFile, // Material ID? else if (!materialTable.empty() && TokenMatch(sz, "matid", 5)) { - SkipSpaces(&sz); - matIdx = ::strtoul10(sz, &sz); + SkipSpaces(&sz, lineEnd); + matIdx = strtoul10(sz, &sz); if (matIdx >= materialTable.size()) { ASSIMP_LOG_ERROR("NFF2: Material index overflow."); matIdx = 0; @@ -534,7 +524,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, shader.specular = mat.specular; shader.shininess = mat.shininess; } else - SkipToken(sz); + SkipToken(sz, lineEnd); } // search the list of all shaders we have for this object whether @@ -556,9 +546,9 @@ void NFFImporter::InternReadFile(const std::string &pFile, // We need to add a new mesh to the list. We assign // an unique name to it to make sure the scene will // pass the validation step for the moment. - // TODO: fix naming of objects in the scenegraph later + // TODO: fix naming of objects in the scene-graph later if (objectName.length()) { - ::strcpy(mesh->name, objectName.c_str()); + ::strncpy(mesh->name, objectName.c_str(), objectName.size()); ASSIMP_itoa10(&mesh->name[objectName.length()], 30, subMeshIdx++); } @@ -656,7 +646,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, sz = &line[1]; out = currentMesh; } - SkipSpaces(sz, &sz); + SkipSpaces(sz, &sz, lineEnd); unsigned int m = strtoul10(sz); // ---- flip the face order @@ -684,13 +674,13 @@ void NFFImporter::InternReadFile(const std::string &pFile, } if (out == currentMeshWithUVCoords) { // FIX: in one test file this wraps over multiple lines - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); if (IsLineEnd(*sz)) { GetNextLine(buffer, line); sz = line; } AI_NFF_PARSE_FLOAT(v.x); - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); if (IsLineEnd(*sz)) { GetNextLine(buffer, line); sz = line; @@ -724,7 +714,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, // if the next one is NOT a number we assume it is a texture file name // this feature is used by some NFF files on the internet and it has // been implemented as it can be really useful - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); if (!IsNumeric(*sz)) { // TODO: Support full file names with spaces and quotation marks ... const char *p = sz; @@ -738,10 +728,8 @@ void NFFImporter::InternReadFile(const std::string &pFile, } else { AI_NFF_PARSE_FLOAT(s.ambient); // optional } - } - // 'shader' - other way to specify a texture - else if (TokenMatch(sz, "shader", 6)) { - SkipSpaces(&sz); + } else if (TokenMatch(sz, "shader", 6)) { // 'shader' - other way to specify a texture + SkipSpaces(&sz, lineEnd); const char *old = sz; while (!IsSpaceOrNewLine(*sz)) ++sz; @@ -896,7 +884,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, } // 'tess' - tessellation else if (TokenMatch(sz, "tess", 4)) { - SkipSpaces(&sz); + SkipSpaces(&sz, lineEnd); iTesselation = strtoul10(sz); } // 'from' - camera position @@ -936,7 +924,7 @@ void NFFImporter::InternReadFile(const std::string &pFile, // '' - comment else if ('#' == line[0]) { const char *space; - SkipSpaces(&line[1], &space); + SkipSpaces(&line[1], &space, lineEnd); if (!IsLineEnd(*space)) { ASSIMP_LOG_INFO(space); } @@ -1165,4 +1153,6 @@ void NFFImporter::InternReadFile(const std::string &pFile, pScene->mRootNode = root; } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.h b/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.h index 0fa615b19..dfa6a882f 100644 --- a/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.h +++ b/Engine/lib/assimp/code/AssetLib/NFF/NFFLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,8 +63,8 @@ namespace Assimp { */ class NFFImporter : public BaseImporter { public: - NFFImporter(); - ~NFFImporter() override; + NFFImporter() = default; + ~NFFImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -107,7 +107,7 @@ private: aiColor3D color, diffuse, specular, ambient, emissive; ai_real refracti; - std::string texFile; + std::string texFile; bool twoSided; // For NFF2 bool shaded; ai_real opacity, shininess; diff --git a/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.cpp b/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.cpp index f137f992d..a3feaa53c 100644 --- a/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of the OFF importer class */ - #ifndef ASSIMP_BUILD_NO_OFF_IMPORTER // internal headers @@ -56,9 +55,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "OFF Importer", "", "", @@ -71,99 +70,92 @@ static const aiImporterDesc desc = { "off" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -OFFImporter::OFFImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -OFFImporter::~OFFImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const -{ - static const char* tokens[] = { "off" }; - return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens),3); +bool OFFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "off" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 3); } // ------------------------------------------------------------------------------------------------ -const aiImporterDesc* OFFImporter::GetInfo () const -{ +const aiImporterDesc *OFFImporter::GetInfo() const { return &desc; } - // skip blank space, lines and comments -static void NextToken(const char **car, const char* end) { - SkipSpacesAndLineEnd(car); - while (*car < end && (**car == '#' || **car == '\n' || **car == '\r')) { - SkipLine(car); - SkipSpacesAndLineEnd(car); - } +static void NextToken(const char **car, const char *end) { + SkipSpacesAndLineEnd(car, end); + while (*car < end && (**car == '#' || **car == '\n' || **car == '\r')) { + SkipLine(car, end); + SkipSpacesAndLineEnd(car, end); + } } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { - std::unique_ptr file( pIOHandler->Open( pFile, "rb")); +void OFFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if( file.get() == nullptr) { - throw DeadlyImportError( "Failed to open OFF file ", pFile, "."); + if (file == nullptr) { + throw DeadlyImportError("Failed to open OFF file ", pFile, "."); } // allocate storage and copy the contents of the file to a memory buffer std::vector mBuffer2; - TextFileToBuffer(file.get(),mBuffer2); - const char* buffer = &mBuffer2[0]; + TextFileToBuffer(file.get(), mBuffer2); + const char *buffer = &mBuffer2[0]; // Proper OFF header parser. We only implement normal loading for now. bool hasTexCoord = false, hasNormals = false, hasColors = false; bool hasHomogenous = false, hasDimension = false; unsigned int dimensions = 3; - const char* car = buffer; - const char* end = buffer + mBuffer2.size(); + const char *car = buffer; + const char *end = buffer + mBuffer2.size(); NextToken(&car, end); if (car < end - 2 && car[0] == 'S' && car[1] == 'T') { - hasTexCoord = true; car += 2; + hasTexCoord = true; + car += 2; } if (car < end - 1 && car[0] == 'C') { - hasColors = true; car++; + hasColors = true; + car++; } - if (car < end- 1 && car[0] == 'N') { - hasNormals = true; car++; + if (car < end - 1 && car[0] == 'N') { + hasNormals = true; + car++; } if (car < end - 1 && car[0] == '4') { - hasHomogenous = true; car++; + hasHomogenous = true; + car++; } if (car < end - 1 && car[0] == 'n') { - hasDimension = true; car++; + hasDimension = true; + car++; } if (car < end - 3 && car[0] == 'O' && car[1] == 'F' && car[2] == 'F') { car += 3; - NextToken(&car, end); + NextToken(&car, end); } else { - // in case there is no OFF header (which is allowed by the - // specification...), then we might have unintentionally read an - // additional dimension from the primitive count fields - dimensions = 3; - hasHomogenous = false; - NextToken(&car, end); + // in case there is no OFF header (which is allowed by the + // specification...), then we might have unintentionally read an + // additional dimension from the primitive count fields + dimensions = 3; + hasHomogenous = false; + NextToken(&car, end); - // at this point the next token should be an integer number - if (car >= end - 1 || *car < '0' || *car > '9') { - throw DeadlyImportError("OFF: Header is invalid"); - } + // at this point the next token should be an integer number + if (car >= end - 1 || *car < '0' || *car > '9') { + throw DeadlyImportError("OFF: Header is invalid"); + } } if (hasDimension) { dimensions = strtoul10(car, &car); - NextToken(&car, end); + NextToken(&car, end); } if (dimensions > 3) { - throw DeadlyImportError - ("OFF: Number of vertex coordinates higher than 3 unsupported"); + throw DeadlyImportError("OFF: Number of vertex coordinates higher than 3 unsupported"); } NextToken(&car, end); @@ -171,7 +163,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS NextToken(&car, end); const unsigned int numFaces = strtoul10(car, &car); NextToken(&car, end); - strtoul10(car, &car); // skip edge count + strtoul10(car, &car); // skip edge count NextToken(&car, end); if (!numVertices) { @@ -182,13 +174,13 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS } pScene->mNumMeshes = 1; - pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes ]; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; - aiMesh* mesh = new aiMesh(); + aiMesh *mesh = new aiMesh(); pScene->mMeshes[0] = mesh; mesh->mNumFaces = numFaces; - aiFace* faces = new aiFace[mesh->mNumFaces]; + aiFace *faces = new aiFace[mesh->mNumFaces]; mesh->mFaces = faces; mesh->mNumVertices = numVertices; @@ -203,102 +195,105 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS char line[4096]; buffer = car; const char *sz = car; + const char *lineEnd = &line[4096]; // now read all vertex lines for (unsigned int i = 0; i < numVertices; ++i) { - if(!GetNextLine(buffer, line)) { + if (!GetNextLine(buffer, line)) { ASSIMP_LOG_ERROR("OFF: The number of verts in the header is incorrect"); break; } - aiVector3D& v = mesh->mVertices[i]; + aiVector3D &v = mesh->mVertices[i]; sz = line; - // helper array to write a for loop over possible dimension values - ai_real* vec[3] = {&v.x, &v.y, &v.z}; + // helper array to write a for loop over possible dimension values + ai_real *vec[3] = { &v.x, &v.y, &v.z }; - // stop at dimensions: this allows loading 1D or 2D coordinate vertices - for (unsigned int dim = 0; dim < dimensions; ++dim ) { - SkipSpaces(&sz); - sz = fast_atoreal_move(sz, *vec[dim]); - } + // stop at dimensions: this allows loading 1D or 2D coordinate vertices + for (unsigned int dim = 0; dim < dimensions; ++dim) { + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, *vec[dim]); + } - // if has homogeneous coordinate, divide others by this one - if (hasHomogenous) { - SkipSpaces(&sz); - ai_real w = 1.; - sz = fast_atoreal_move(sz, w); - for (unsigned int dim = 0; dim < dimensions; ++dim ) { - *(vec[dim]) /= w; - } - } + // if has homogeneous coordinate, divide others by this one + if (hasHomogenous) { + SkipSpaces(&sz, lineEnd); + ai_real w = 1.; + sz = fast_atoreal_move(sz, w); + for (unsigned int dim = 0; dim < dimensions; ++dim) { + *(vec[dim]) /= w; + } + } - // read optional normals - if (hasNormals) { - aiVector3D& n = mesh->mNormals[i]; - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)n.x); - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)n.y); - SkipSpaces(&sz); - fast_atoreal_move(sz,(ai_real&)n.z); - } + // read optional normals + if (hasNormals) { + aiVector3D &n = mesh->mNormals[i]; + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)n.x); + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)n.y); + SkipSpaces(&sz, lineEnd); + fast_atoreal_move(sz, (ai_real &)n.z); + } - // reading colors is a pain because the specification says it can be - // integers or floats, and any number of them between 1 and 4 included, - // until the next comment or end of line - // in theory should be testing type ! - if (hasColors) { - aiColor4D& c = mesh->mColors[0][i]; - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)c.r); + // reading colors is a pain because the specification says it can be + // integers or floats, and any number of them between 1 and 4 included, + // until the next comment or end of line + // in theory should be testing type ! + if (hasColors) { + aiColor4D &c = mesh->mColors[0][i]; + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)c.r); if (*sz != '#' && *sz != '\n' && *sz != '\r') { - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)c.g); + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)c.g); } else { - c.g = 0.; - } + c.g = 0.; + } if (*sz != '#' && *sz != '\n' && *sz != '\r') { - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)c.b); + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)c.b); } else { - c.b = 0.; - } + c.b = 0.; + } if (*sz != '#' && *sz != '\n' && *sz != '\r') { - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)c.a); + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)c.a); } else { - c.a = 1.; - } - } + c.a = 1.; + } + } if (hasTexCoord) { - aiVector3D& t = mesh->mTextureCoords[0][i]; - SkipSpaces(&sz); - sz = fast_atoreal_move(sz,(ai_real&)t.x); - SkipSpaces(&sz); - fast_atoreal_move(sz,(ai_real&)t.y); - } + aiVector3D &t = mesh->mTextureCoords[0][i]; + SkipSpaces(&sz, lineEnd); + sz = fast_atoreal_move(sz, (ai_real &)t.x); + SkipSpaces(&sz, lineEnd); + fast_atoreal_move(sz, (ai_real &)t.y); + } } // load faces with their indices faces = mesh->mFaces; - for (unsigned int i = 0; i < numFaces; ) { - if(!GetNextLine(buffer,line)) { + for (unsigned int i = 0; i < numFaces;) { + if (!GetNextLine(buffer, line)) { ASSIMP_LOG_ERROR("OFF: The number of faces in the header is incorrect"); - break; + throw DeadlyImportError("OFF: The number of faces in the header is incorrect"); } unsigned int idx; - sz = line; SkipSpaces(&sz); - idx = strtoul10(sz,&sz); - if(!idx || idx > 9) { - ASSIMP_LOG_ERROR("OFF: Faces with zero indices aren't allowed"); + sz = line; + SkipSpaces(&sz, lineEnd); + idx = strtoul10(sz, &sz); + if (!idx || idx > 9) { + ASSIMP_LOG_ERROR("OFF: Faces with zero indices aren't allowed"); --mesh->mNumFaces; + ++i; continue; - } - faces->mNumIndices = idx; + } + faces->mNumIndices = idx; faces->mIndices = new unsigned int[faces->mNumIndices]; - for (unsigned int m = 0; m < faces->mNumIndices;++m) { - SkipSpaces(&sz); - idx = strtoul10(sz,&sz); + for (unsigned int m = 0; m < faces->mNumIndices; ++m) { + SkipSpaces(&sz, lineEnd); + idx = strtoul10(sz, &sz); if (idx >= numVertices) { ASSIMP_LOG_ERROR("OFF: Vertex index is out of range"); idx = numVertices - 1; @@ -313,20 +308,22 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS pScene->mRootNode = new aiNode(); pScene->mRootNode->mName.Set(""); pScene->mRootNode->mNumMeshes = 1; - pScene->mRootNode->mMeshes = new unsigned int [pScene->mRootNode->mNumMeshes]; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; pScene->mRootNode->mMeshes[0] = 0; // generate a default material pScene->mNumMaterials = 1; - pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; - aiMaterial* pcMat = new aiMaterial(); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + aiMaterial *pcMat = new aiMaterial(); - aiColor4D clr( ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 1.0 ) ); - pcMat->AddProperty(&clr,1,AI_MATKEY_COLOR_DIFFUSE); + aiColor4D clr(0.6f, 0.6f, 0.6f, 1.0f); + pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); pScene->mMaterials[0] = pcMat; const int twosided = 1; pcMat->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED); } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_OFF_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.h b/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.h index 04bb7f481..02829d360 100644 --- a/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.h +++ b/Engine/lib/assimp/code/AssetLib/OFF/OFFLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,8 +57,8 @@ namespace Assimp { */ class OFFImporter : public BaseImporter { public: - OFFImporter(); - ~OFFImporter() override; + OFFImporter() = default; + ~OFFImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.cpp b/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.cpp index a5d8325fc..a7b6352af 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.cpp @@ -59,9 +59,9 @@ namespace Assimp { // ------------------------------------------------------------------------------------------------ // Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp -void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { +void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) { // invoke the exporter - ObjExporter exporter(pFile, pScene); + ObjExporter exporter(pFile, pScene, false, props); if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) { throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); @@ -86,9 +86,9 @@ void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene // ------------------------------------------------------------------------------------------------ // Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp -void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* ) { +void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) { // invoke the exporter - ObjExporter exporter(pFile, pScene, true); + ObjExporter exporter(pFile, pScene, true, props); if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) { throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); @@ -111,7 +111,7 @@ void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* p static const std::string MaterialExt = ".mtl"; // ------------------------------------------------------------------------------------------------ -ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl) +ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl, const ExportProperties* props) : filename(_filename) , pScene(pScene) , vn() @@ -130,7 +130,10 @@ ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMt mOutputMat.imbue(l); mOutputMat.precision(ASSIMP_AI_REAL_TEXT_PRECISION); - WriteGeometryFile(noMtl); + WriteGeometryFile( + noMtl, + props == nullptr ? true : props->GetPropertyBool("bJoinIdenticalVertices", true) + ); if ( !noMtl ) { WriteMaterialFile(); } @@ -171,9 +174,12 @@ void ObjExporter::WriteHeader(std::ostringstream& out) { // ------------------------------------------------------------------------------------------------ std::string ObjExporter::GetMaterialName(unsigned int index) { + static const std::string EmptyStr; + if ( nullptr == pScene->mMaterials ) { + return EmptyStr; + } const aiMaterial* const mat = pScene->mMaterials[index]; if ( nullptr == mat ) { - static const std::string EmptyStr; return EmptyStr; } @@ -255,14 +261,14 @@ void ObjExporter::WriteMaterialFile() { } } -void ObjExporter::WriteGeometryFile(bool noMtl) { +void ObjExporter::WriteGeometryFile(bool noMtl, bool merge_identical_vertices) { WriteHeader(mOutput); if (!noMtl) mOutput << "mtllib " << GetMaterialLibName() << endl << endl; // collect mesh geometry aiMatrix4x4 mBase; - AddNode(pScene->mRootNode, mBase); + AddNode(pScene->mRootNode, mBase, merge_identical_vertices); // write vertex positions with colors, if any mVpMap.getKeys( vp ); @@ -330,7 +336,7 @@ void ObjExporter::WriteGeometryFile(bool noMtl) { } // ------------------------------------------------------------------------------------------------ -void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) { +void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat, bool merge_identical_vertices) { mMeshes.emplace_back(); MeshInstance& mesh = mMeshes.back(); @@ -362,13 +368,14 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4 for(unsigned int a = 0; a < f.mNumIndices; ++a) { const unsigned int idx = f.mIndices[a]; + const unsigned int fi = merge_identical_vertices ? 0 : idx; aiVector3D vert = mat * m->mVertices[idx]; if ( nullptr != m->mColors[ 0 ] ) { aiColor4D col4 = m->mColors[ 0 ][ idx ]; - face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b)}); + face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b), fi}); } else { - face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0)}); + face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0), fi}); } if (m->mNormals) { @@ -388,21 +395,21 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4 } // ------------------------------------------------------------------------------------------------ -void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent) { +void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent, bool merge_identical_vertices) { const aiMatrix4x4& mAbs = mParent * nd->mTransformation; aiMesh *cm( nullptr ); for(unsigned int i = 0; i < nd->mNumMeshes; ++i) { cm = pScene->mMeshes[nd->mMeshes[i]]; if (nullptr != cm) { - AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs); + AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices); } else { - AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs); + AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices); } } for(unsigned int i = 0; i < nd->mNumChildren; ++i) { - AddNode(nd->mChildren[i], mAbs); + AddNode(nd->mChildren[i], mAbs, merge_identical_vertices); } } diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.h b/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.h index a64f38f74..4c92aa16f 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjExporter.h @@ -51,6 +51,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + struct aiScene; struct aiNode; struct aiMesh; @@ -63,7 +65,7 @@ namespace Assimp { class ObjExporter { public: /// Constructor for a specific scene to export - ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false); + ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false, const ExportProperties* props = nullptr); ~ObjExporter(); std::string GetMaterialLibName(); std::string GetMaterialLibFileName(); @@ -97,10 +99,10 @@ private: void WriteHeader(std::ostringstream& out); void WriteMaterialFile(); - void WriteGeometryFile(bool noMtl=false); + void WriteGeometryFile(bool noMtl=false, bool merge_identical_vertices = false); std::string GetMaterialName(unsigned int index); - void AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat); - void AddNode(const aiNode* nd, const aiMatrix4x4& mParent); + void AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat, bool merge_identical_vertices); + void AddNode(const aiNode* nd, const aiMatrix4x4& mParent, bool merge_identical_vertices); private: std::string filename; @@ -109,6 +111,7 @@ private: struct vertexData { aiVector3D vp; aiColor3D vc; // OBJ does not support 4D color + uint32_t index = 0; }; std::vector vn, vt; @@ -133,7 +136,7 @@ private: if (a.vc.g > b.vc.g) return false; if (a.vc.b < b.vc.b) return true; if (a.vc.b > b.vc.b) return false; - return false; + return a.index < b.index; } }; diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileData.h b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileData.h index 82296413b..205c855e5 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileData.h +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -199,12 +199,12 @@ struct Material { //! Constructor Material() : - diffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + diffuse(0.6f, 0.6f, 0.6f), alpha(ai_real(1.0)), shineness(ai_real(0.0)), illumination_model(1), ior(ai_real(1.0)), - transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)), + transparent(1.0f, 1.0, 1.0), roughness(), metallic(), sheen(), @@ -239,8 +239,6 @@ struct Mesh { unsigned int m_uiMaterialIndex; /// True, if normals are stored. bool m_hasNormals; - /// True, if vertex colors are stored. - bool m_hasVertexColors; /// Constructor explicit Mesh(const std::string &name) : diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.cpp b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.cpp index 354c15f4e..252b79beb 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2020, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -54,7 +54,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Wavefront Object Importer", "", "", @@ -67,7 +67,7 @@ static const aiImporterDesc desc = { "obj" }; -static const unsigned int ObjMinSize = 16; +static constexpr unsigned int ObjMinSize = 16u; namespace Assimp { @@ -78,13 +78,14 @@ using namespace std; ObjFileImporter::ObjFileImporter() : m_Buffer(), m_pRootObject(nullptr), - m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) {} + m_strAbsPath(std::string(1, DefaultIOSystem().getOsSeparator())) { + // empty +} // ------------------------------------------------------------------------------------------------ // Destructor. ObjFileImporter::~ObjFileImporter() { delete m_pRootObject; - m_pRootObject = nullptr; } // ------------------------------------------------------------------------------------------------ @@ -102,13 +103,18 @@ const aiImporterDesc *ObjFileImporter::GetInfo() const { // ------------------------------------------------------------------------------------------------ // Obj-file import implementation void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + if (m_pRootObject != nullptr) { + delete m_pRootObject; + m_pRootObject = nullptr; + } + // Read file into memory - static const std::string mode = "rb"; + static constexpr char mode[] = "rb"; auto streamCloser = [&](IOStream *pStream) { pIOHandler->Close(pStream); }; std::unique_ptr fileStream(pIOHandler->Open(file, mode), streamCloser); - if (!fileStream.get()) { + if (!fileStream) { throw DeadlyImportError("Failed to open file ", file, "."); } @@ -157,7 +163,7 @@ void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, I // ------------------------------------------------------------------------------------------------ // Create the data from parsed obj-file void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) { - if (nullptr == pModel) { + if (pModel == nullptr) { return; } @@ -172,7 +178,6 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene } if (!pModel->mObjects.empty()) { - unsigned int meshCount = 0; unsigned int childCount = 0; @@ -187,7 +192,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene pScene->mRootNode->mChildren = new aiNode *[childCount]; // Create nodes for the whole scene - std::vector MeshArray; + std::vector> MeshArray; MeshArray.reserve(meshCount); for (size_t index = 0; index < pModel->mObjects.size(); ++index) { createNodes(pModel, pModel->mObjects[index], pScene->mRootNode, pScene, MeshArray); @@ -199,7 +204,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene if (pScene->mNumMeshes > 0) { pScene->mMeshes = new aiMesh *[MeshArray.size()]; for (size_t index = 0; index < MeshArray.size(); ++index) { - pScene->mMeshes[index] = MeshArray[index]; + pScene->mMeshes[index] = MeshArray[index].release(); } } @@ -251,9 +256,8 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene // Creates all nodes of the model aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject, aiNode *pParent, aiScene *pScene, - std::vector &MeshArray) { - ai_assert(nullptr != pModel); - if (nullptr == pObject) { + std::vector> &MeshArray) { + if (nullptr == pObject || pModel == nullptr) { return nullptr; } @@ -269,12 +273,10 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) { unsigned int meshId = pObject->m_Meshes[i]; - aiMesh *pMesh = createTopology(pModel, pObject, meshId); - if (pMesh) { + std::unique_ptr pMesh = createTopology(pModel, pObject, meshId); + if (pMesh != nullptr) { if (pMesh->mNumFaces > 0) { - MeshArray.push_back(pMesh); - } else { - delete pMesh; + MeshArray.push_back(std::move(pMesh)); } } } @@ -306,17 +308,14 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile // ------------------------------------------------------------------------------------------------ // Create topology data -aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) { - // Checking preconditions - ai_assert(nullptr != pModel); - - if (nullptr == pData) { +std::unique_ptr ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) { + if (nullptr == pData || pModel == nullptr) { return nullptr; } // Create faces ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex]; - if (!pObjMesh) { + if (pObjMesh == nullptr) { return nullptr; } @@ -331,8 +330,10 @@ aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjF for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) { const ObjFile::Face *inp = pObjMesh->m_Faces[index]; - //ai_assert(nullptr != inp); - + if (inp == nullptr) { + continue; + } + if (inp->mPrimitiveType == aiPrimitiveType_LINE) { pMesh->mNumFaces += static_cast(inp->m_vertices.size() - 1); pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE; @@ -349,14 +350,14 @@ aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjF } } - unsigned int uiIdxCount(0u); + unsigned int uiIdxCount = 0u; if (pMesh->mNumFaces > 0) { pMesh->mFaces = new aiFace[pMesh->mNumFaces]; if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) { pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex; } - unsigned int outIndex(0); + unsigned int outIndex = 0u; // Copy all data from all stored meshes for (auto &face : pObjMesh->m_Faces) { @@ -389,7 +390,7 @@ aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjF // Create mesh vertices createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount); - return pMesh.release(); + return pMesh; } // ------------------------------------------------------------------------------------------------ @@ -400,11 +401,14 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel, aiMesh *pMesh, unsigned int numIndices) { // Checking preconditions - ai_assert(nullptr != pCurrentObject); + if (pCurrentObject == nullptr || pModel == nullptr || pMesh == nullptr) { + return; + } // Break, if no faces are stored in object - if (pCurrentObject->m_Meshes.empty()) + if (pCurrentObject->m_Meshes.empty()) { return; + } // Get current mesh ObjFile::Mesh *pObjMesh = pModel->mMeshes[uiMeshIndex]; @@ -500,6 +504,10 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel, if (vertexIndex) { if (!last) { + if (pMesh->mNumVertices <= newIndex + 1) { + throw DeadlyImportError("OBJ: bad vertex index"); + } + pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex]; if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) { pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex]; @@ -579,11 +587,12 @@ void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pSc it = pModel->mMaterialMap.find(pModel->mMaterialLib[matIndex]); // No material found, use the default material - if (pModel->mMaterialMap.end() == it) + if (pModel->mMaterialMap.end() == it) { continue; + } aiMaterial *mat = new aiMaterial; - ObjFile::Material *pCurrentMaterial = (*it).second; + ObjFile::Material *pCurrentMaterial = it->second; mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME); // convert illumination model @@ -770,8 +779,11 @@ void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pSc // Appends this node to the parent node void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) { // Checking preconditions - ai_assert(nullptr != pParent); - ai_assert(nullptr != pChild); + if (pParent == nullptr || pChild == nullptr) { + ai_assert(nullptr != pParent); + ai_assert(nullptr != pChild); + return; + } // Assign parent to child pChild->mParent = pParent; diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.h b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.h index e76c27950..6768013e4 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.h +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileImporter.h @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include struct aiMesh; @@ -84,10 +85,10 @@ protected: //! \brief Creates all nodes stored in imported content. aiNode *createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pData, - aiNode *pParent, aiScene *pScene, std::vector &MeshArray); + aiNode *pParent, aiScene *pScene, std::vector> &MeshArray); //! \brief Creates topology data like faces and meshes for the geometry. - aiMesh *createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, + std::unique_ptr createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int uiMeshIndex); //! \brief Creates vertices from model. diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp index a0f6035ac..effdf627f 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileMtlImporter.cpp @@ -53,38 +53,38 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { // Material specific token (case insensitive compare) -static const std::string DiffuseTexture = "map_Kd"; -static const std::string AmbientTexture = "map_Ka"; -static const std::string SpecularTexture = "map_Ks"; -static const std::string OpacityTexture = "map_d"; -static const std::string EmissiveTexture1 = "map_emissive"; -static const std::string EmissiveTexture2 = "map_Ke"; -static const std::string BumpTexture1 = "map_bump"; -static const std::string BumpTexture2 = "bump"; -static const std::string NormalTextureV1 = "map_Kn"; -static const std::string NormalTextureV2 = "norm"; -static const std::string ReflectionTexture = "refl"; -static const std::string DisplacementTexture1 = "map_disp"; -static const std::string DisplacementTexture2 = "disp"; -static const std::string SpecularityTexture = "map_ns"; -static const std::string RoughnessTexture = "map_Pr"; -static const std::string MetallicTexture = "map_Pm"; -static const std::string SheenTexture = "map_Ps"; -static const std::string RMATexture = "map_Ps"; +static constexpr char DiffuseTexture[] = "map_Kd"; +static constexpr char AmbientTexture[] = "map_Ka"; +static constexpr char SpecularTexture[] = "map_Ks"; +static constexpr char OpacityTexture[] = "map_d"; +static constexpr char EmissiveTexture1[] = "map_emissive"; +static constexpr char EmissiveTexture2[] = "map_Ke"; +static constexpr char BumpTexture1[] = "map_bump"; +static constexpr char BumpTexture2[] = "bump"; +static constexpr char NormalTextureV1[] = "map_Kn"; +static constexpr char NormalTextureV2[] = "norm"; +static constexpr char ReflectionTexture[] = "refl"; +static constexpr char DisplacementTexture1[] = "map_disp"; +static constexpr char DisplacementTexture2[] = "disp"; +static constexpr char SpecularityTexture[] = "map_ns"; +static constexpr char RoughnessTexture[] = "map_Pr"; +static constexpr char MetallicTexture[] = "map_Pm"; +static constexpr char SheenTexture[] = "map_Ps"; +static constexpr char RMATexture[] = "map_Ps"; // texture option specific token -static const std::string BlendUOption = "-blendu"; -static const std::string BlendVOption = "-blendv"; -static const std::string BoostOption = "-boost"; -static const std::string ModifyMapOption = "-mm"; -static const std::string OffsetOption = "-o"; -static const std::string ScaleOption = "-s"; -static const std::string TurbulenceOption = "-t"; -static const std::string ResolutionOption = "-texres"; -static const std::string ClampOption = "-clamp"; -static const std::string BumpOption = "-bm"; -static const std::string ChannelOption = "-imfchan"; -static const std::string TypeOption = "-type"; +static constexpr char BlendUOption[] = "-blendu"; +static constexpr char BlendVOption[] = "-blendv"; +static constexpr char BoostOption[] = "-boost"; +static constexpr char ModifyMapOption[] = "-mm"; +static constexpr char OffsetOption[] = "-o"; +static constexpr char ScaleOption[] = "-s"; +static constexpr char TurbulenceOption[] = "-t"; +static constexpr char ResolutionOption[] = "-texres"; +static constexpr char ClampOption[] = "-clamp"; +static constexpr char BumpOption[] = "-bm"; +static constexpr char ChannelOption[] = "-imfchan"; +static constexpr char TypeOption[] = "-type"; // ------------------------------------------------------------------- // Constructor @@ -252,9 +252,9 @@ void ObjFileMtlImporter::load() { case 'a': // Anisotropy { ++m_DataIt; - getFloatValue(m_pModel->mCurrentMaterial->anisotropy); if (m_pModel->mCurrentMaterial != nullptr) - m_DataIt = skipLine(m_DataIt, m_DataItEnd, m_uiLine); + getFloatValue(m_pModel->mCurrentMaterial->anisotropy); + m_DataIt = skipLine(m_DataIt, m_DataItEnd, m_uiLine); } break; default: { @@ -282,6 +282,7 @@ void ObjFileMtlImporter::getColorRGBA(aiColor3D *pColor) { pColor->b = b; } +// ------------------------------------------------------------------- void ObjFileMtlImporter::getColorRGBA(Maybe &value) { aiColor3D v; getColorRGBA(&v); @@ -309,6 +310,7 @@ void ObjFileMtlImporter::getFloatValue(ai_real &value) { value = (ai_real)fast_atof(&m_buffer[0]); } +// ------------------------------------------------------------------- void ObjFileMtlImporter::getFloatValue(Maybe &value) { m_DataIt = CopyNextWord(m_DataIt, m_DataItEnd, &m_buffer[0], BUFFERSIZE); size_t len = std::strlen(&m_buffer[0]); @@ -341,7 +343,7 @@ void ObjFileMtlImporter::createMaterial() { } } - name = trim_whitespaces(name); + name = ai_trim(name); std::map::iterator it = m_pModel->mMaterialMap.find(name); if (m_pModel->mMaterialMap.end() == it) { @@ -356,73 +358,79 @@ void ObjFileMtlImporter::createMaterial() { } } else { // Use older material - m_pModel->mCurrentMaterial = (*it).second; + m_pModel->mCurrentMaterial = it->second; } } // ------------------------------------------------------------------- // Gets a texture name from data. void ObjFileMtlImporter::getTexture() { - aiString *out(nullptr); + aiString *out = nullptr; int clampIndex = -1; + if (m_pModel->mCurrentMaterial == nullptr) { + m_pModel->mCurrentMaterial = new ObjFile::Material(); + m_pModel->mCurrentMaterial->MaterialName.Set("Empty_Material"); + m_pModel->mMaterialMap["Empty_Material"] = m_pModel->mCurrentMaterial; + } + const char *pPtr(&(*m_DataIt)); - if (!ASSIMP_strincmp(pPtr, DiffuseTexture.c_str(), static_cast(DiffuseTexture.size()))) { + if (!ASSIMP_strincmp(pPtr, DiffuseTexture, static_cast(strlen(DiffuseTexture)))) { // Diffuse texture out = &m_pModel->mCurrentMaterial->texture; clampIndex = ObjFile::Material::TextureDiffuseType; - } else if (!ASSIMP_strincmp(pPtr, AmbientTexture.c_str(), static_cast(AmbientTexture.size()))) { + } else if (!ASSIMP_strincmp(pPtr, AmbientTexture, static_cast(strlen(AmbientTexture)))) { // Ambient texture out = &m_pModel->mCurrentMaterial->textureAmbient; clampIndex = ObjFile::Material::TextureAmbientType; - } else if (!ASSIMP_strincmp(pPtr, SpecularTexture.c_str(), static_cast(SpecularTexture.size()))) { + } else if (!ASSIMP_strincmp(pPtr, SpecularTexture, static_cast(strlen(SpecularTexture)))) { // Specular texture out = &m_pModel->mCurrentMaterial->textureSpecular; clampIndex = ObjFile::Material::TextureSpecularType; - } else if (!ASSIMP_strincmp(pPtr, DisplacementTexture1.c_str(), static_cast(DisplacementTexture1.size())) || - !ASSIMP_strincmp(pPtr, DisplacementTexture2.c_str(), static_cast(DisplacementTexture2.size()))) { + } else if (!ASSIMP_strincmp(pPtr, DisplacementTexture1, static_cast(strlen(DisplacementTexture1))) || + !ASSIMP_strincmp(pPtr, DisplacementTexture2, static_cast(strlen(DisplacementTexture2)))) { // Displacement texture out = &m_pModel->mCurrentMaterial->textureDisp; clampIndex = ObjFile::Material::TextureDispType; - } else if (!ASSIMP_strincmp(pPtr, OpacityTexture.c_str(), static_cast(OpacityTexture.size()))) { + } else if (!ASSIMP_strincmp(pPtr, OpacityTexture, static_cast(strlen(OpacityTexture)))) { // Opacity texture out = &m_pModel->mCurrentMaterial->textureOpacity; clampIndex = ObjFile::Material::TextureOpacityType; - } else if (!ASSIMP_strincmp(pPtr, EmissiveTexture1.c_str(), static_cast(EmissiveTexture1.size())) || - !ASSIMP_strincmp(pPtr, EmissiveTexture2.c_str(), static_cast(EmissiveTexture2.size()))) { + } else if (!ASSIMP_strincmp(pPtr, EmissiveTexture1, static_cast(strlen(EmissiveTexture1))) || + !ASSIMP_strincmp(pPtr, EmissiveTexture2, static_cast(strlen(EmissiveTexture2)))) { // Emissive texture out = &m_pModel->mCurrentMaterial->textureEmissive; clampIndex = ObjFile::Material::TextureEmissiveType; - } else if (!ASSIMP_strincmp(pPtr, BumpTexture1.c_str(), static_cast(BumpTexture1.size())) || - !ASSIMP_strincmp(pPtr, BumpTexture2.c_str(), static_cast(BumpTexture2.size()))) { + } else if (!ASSIMP_strincmp(pPtr, BumpTexture1, static_cast(strlen(BumpTexture1))) || + !ASSIMP_strincmp(pPtr, BumpTexture2, static_cast(strlen(BumpTexture2)))) { // Bump texture out = &m_pModel->mCurrentMaterial->textureBump; clampIndex = ObjFile::Material::TextureBumpType; - } else if (!ASSIMP_strincmp(pPtr, NormalTextureV1.c_str(), static_cast(NormalTextureV1.size())) || !ASSIMP_strincmp(pPtr, NormalTextureV2.c_str(), static_cast(NormalTextureV2.size()))) { + } else if (!ASSIMP_strincmp(pPtr, NormalTextureV1, static_cast(strlen(NormalTextureV1))) || !ASSIMP_strincmp(pPtr, NormalTextureV2, static_cast(strlen(NormalTextureV2)))) { // Normal map out = &m_pModel->mCurrentMaterial->textureNormal; clampIndex = ObjFile::Material::TextureNormalType; - } else if (!ASSIMP_strincmp(pPtr, ReflectionTexture.c_str(), static_cast(ReflectionTexture.size()))) { + } else if (!ASSIMP_strincmp(pPtr, ReflectionTexture, static_cast(strlen(ReflectionTexture)))) { // Reflection texture(s) //Do nothing here return; - } else if (!ASSIMP_strincmp(pPtr, SpecularityTexture.c_str(), static_cast(SpecularityTexture.size()))) { + } else if (!ASSIMP_strincmp(pPtr, SpecularityTexture, static_cast(strlen(SpecularityTexture)))) { // Specularity scaling (glossiness) out = &m_pModel->mCurrentMaterial->textureSpecularity; clampIndex = ObjFile::Material::TextureSpecularityType; - } else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture.c_str(), static_cast(RoughnessTexture.size()))) { + } else if ( !ASSIMP_strincmp( pPtr, RoughnessTexture, static_cast(strlen(RoughnessTexture)))) { // PBR Roughness texture out = & m_pModel->mCurrentMaterial->textureRoughness; clampIndex = ObjFile::Material::TextureRoughnessType; - } else if ( !ASSIMP_strincmp( pPtr, MetallicTexture.c_str(), static_cast(MetallicTexture.size()))) { + } else if ( !ASSIMP_strincmp( pPtr, MetallicTexture, static_cast(strlen(MetallicTexture)))) { // PBR Metallic texture out = & m_pModel->mCurrentMaterial->textureMetallic; clampIndex = ObjFile::Material::TextureMetallicType; - } else if (!ASSIMP_strincmp( pPtr, SheenTexture.c_str(), static_cast(SheenTexture.size()))) { + } else if (!ASSIMP_strincmp( pPtr, SheenTexture, static_cast(strlen(SheenTexture)))) { // PBR Sheen (reflectance) texture out = & m_pModel->mCurrentMaterial->textureSheen; clampIndex = ObjFile::Material::TextureSheenType; - } else if (!ASSIMP_strincmp( pPtr, RMATexture.c_str(), static_cast(RMATexture.size()))) { + } else if (!ASSIMP_strincmp( pPtr, RMATexture, static_cast(strlen(RMATexture)))) { // PBR Rough/Metal/AO texture out = & m_pModel->mCurrentMaterial->textureRMA; clampIndex = ObjFile::Material::TextureRMAType; @@ -466,7 +474,7 @@ void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString //skip option key and value int skipToken = 1; - if (!ASSIMP_strincmp(pPtr, ClampOption.c_str(), static_cast(ClampOption.size()))) { + if (!ASSIMP_strincmp(pPtr, ClampOption, static_cast(strlen(ClampOption)))) { DataArrayIt it = getNextToken(m_DataIt, m_DataItEnd); char value[3]; CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value)); @@ -475,7 +483,7 @@ void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString } skipToken = 2; - } else if (!ASSIMP_strincmp(pPtr, TypeOption.c_str(), static_cast(TypeOption.size()))) { + } else if (!ASSIMP_strincmp(pPtr, TypeOption, static_cast(strlen(TypeOption)))) { DataArrayIt it = getNextToken(m_DataIt, m_DataItEnd); char value[12]; CopyNextWord(it, m_DataItEnd, value, sizeof(value) / sizeof(*value)); @@ -503,15 +511,21 @@ void ObjFileMtlImporter::getTextureOption(bool &clamp, int &clampIndex, aiString } skipToken = 2; - } else if (!ASSIMP_strincmp(pPtr, BumpOption.c_str(), static_cast(BumpOption.size()))) { + } else if (!ASSIMP_strincmp(pPtr, BumpOption, static_cast(strlen(BumpOption)))) { DataArrayIt it = getNextToken(m_DataIt, m_DataItEnd); getFloat(it, m_DataItEnd, m_pModel->mCurrentMaterial->bump_multiplier); skipToken = 2; - } else if (!ASSIMP_strincmp(pPtr, BlendUOption.c_str(), static_cast(BlendUOption.size())) || !ASSIMP_strincmp(pPtr, BlendVOption.c_str(), static_cast(BlendVOption.size())) || !ASSIMP_strincmp(pPtr, BoostOption.c_str(), static_cast(BoostOption.size())) || !ASSIMP_strincmp(pPtr, ResolutionOption.c_str(), static_cast(ResolutionOption.size())) || !ASSIMP_strincmp(pPtr, ChannelOption.c_str(), static_cast(ChannelOption.size()))) { + } else if (!ASSIMP_strincmp(pPtr, BlendUOption, static_cast(strlen(BlendUOption))) || + !ASSIMP_strincmp(pPtr, BlendVOption, static_cast(strlen(BlendVOption))) || + !ASSIMP_strincmp(pPtr, BoostOption, static_cast(strlen(BoostOption))) || + !ASSIMP_strincmp(pPtr, ResolutionOption, static_cast(strlen(ResolutionOption))) || + !ASSIMP_strincmp(pPtr, ChannelOption, static_cast(strlen(ChannelOption)))) { skipToken = 2; - } else if (!ASSIMP_strincmp(pPtr, ModifyMapOption.c_str(), static_cast(ModifyMapOption.size()))) { + } else if (!ASSIMP_strincmp(pPtr, ModifyMapOption, static_cast(strlen(ModifyMapOption)))) { skipToken = 3; - } else if (!ASSIMP_strincmp(pPtr, OffsetOption.c_str(), static_cast(OffsetOption.size())) || !ASSIMP_strincmp(pPtr, ScaleOption.c_str(), static_cast(ScaleOption.size())) || !ASSIMP_strincmp(pPtr, TurbulenceOption.c_str(), static_cast(TurbulenceOption.size()))) { + } else if (!ASSIMP_strincmp(pPtr, OffsetOption, static_cast(strlen(OffsetOption))) || + !ASSIMP_strincmp(pPtr, ScaleOption, static_cast(strlen(ScaleOption))) || + !ASSIMP_strincmp(pPtr, TurbulenceOption, static_cast(strlen(TurbulenceOption)))) { skipToken = 4; } diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.cpp b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.cpp index 42bd23689..fec1fe87b 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,7 +52,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include namespace Assimp { @@ -64,6 +63,7 @@ ObjFileParser::ObjFileParser() : m_pModel(nullptr), m_uiLine(0), m_buffer(), + mEnd(&m_buffer[Buffersize]), m_pIO(nullptr), m_progress(nullptr), m_originalObjFileName() { @@ -97,8 +97,6 @@ ObjFileParser::ObjFileParser(IOStreamBuffer &streamBuffer, const std::stri parseFile(streamBuffer); } -ObjFileParser::~ObjFileParser() = default; - void ObjFileParser::setBuffer(std::vector &buffer) { m_DataIt = buffer.begin(); m_DataItEnd = buffer.end(); @@ -121,6 +119,7 @@ void ObjFileParser::parseFile(IOStreamBuffer &streamBuffer) { while (streamBuffer.getNextDataLine(buffer, '\\')) { m_DataIt = buffer.begin(); m_DataItEnd = buffer.end(); + mEnd = &buffer[buffer.size() - 1] + 1; // Handle progress reporting const size_t filePos(streamBuffer.getFilePos()); @@ -130,7 +129,7 @@ void ObjFileParser::parseFile(IOStreamBuffer &streamBuffer) { m_progress->UpdateFileRead(processed, progressTotal); } - // handle cstype section end (http://paulbourke.net/dataformats/obj/) + // handle c-stype section end (http://paulbourke.net/dataformats/obj/) if (insideCstype) { switch (*m_DataIt) { case 'e': { @@ -156,9 +155,17 @@ void ObjFileParser::parseFile(IOStreamBuffer &streamBuffer) { // read in vertex definition (homogeneous coords) getHomogeneousVector3(m_pModel->mVertices); } else if (numComponents == 6) { + // fill previous omitted vertex-colors by default + if (m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) { + m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0)); + } // read vertex and vertex-color getTwoVectors3(m_pModel->mVertices, m_pModel->mVertexColors); } + // append omitted vertex-colors as default for the end if any vertex-color exists + if (!m_pModel->mVertexColors.empty() && m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) { + m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0)); + } } else if (*m_DataIt == 't') { // read in texture coordinate ( 2D or 3D ) ++m_DataIt; @@ -236,7 +243,7 @@ void ObjFileParser::parseFile(IOStreamBuffer &streamBuffer) { getNameNoSpace(m_DataIt, m_DataItEnd, name); insideCstype = name == "cstype"; goto pf_skip_line; - } break; + } default: { pf_skip_line: @@ -293,18 +300,19 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() { } else if (IsLineEnd(*tmp)) { end_of_definition = true; } - if (!SkipSpaces(&tmp)) { + if (!SkipSpaces(&tmp, mEnd)) { break; } const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp)); - SkipToken(tmp); + SkipToken(tmp, mEnd); if (isNum) { ++numComponents; } - if (!SkipSpaces(&tmp)) { + if (!SkipSpaces(&tmp, mEnd)) { break; } } + return numComponents; } @@ -440,7 +448,7 @@ void ObjFileParser::getFace(aiPrimitiveType type) { const bool vt = (!m_pModel->mTextureCoord.empty()); const bool vn = (!m_pModel->mNormals.empty()); int iPos = 0; - while (m_DataIt != m_DataItEnd) { + while (m_DataIt < m_DataItEnd) { int iStep = 1; if (IsLineEnd(*m_DataIt)) { @@ -456,9 +464,20 @@ void ObjFileParser::getFace(aiPrimitiveType type) { iPos = 0; } else { //OBJ USES 1 Base ARRAYS!!!! - const char *token = &(*m_DataIt); - const int iVal = ::atoi(token); - + int iVal; + auto end = m_DataIt; + // find either the buffer end or the '\0' + while (end < m_DataItEnd && *end != '\0') + ++end; + // avoid temporary string allocation if there is a zero + if (end != m_DataItEnd) { + iVal = ::atoi(&(*m_DataIt)); + } else { + // otherwise make a zero terminated copy, which is safe to pass to atoi + std::string number(&(*m_DataIt), m_DataItEnd - m_DataIt); + iVal = ::atoi(number.c_str()); + } + // increment iStep position based off of the sign and # of digits int tmp = iVal; if (iVal < 0) { @@ -468,8 +487,9 @@ void ObjFileParser::getFace(aiPrimitiveType type) { ++iStep; } - if (iPos == 1 && !vt && vn) + if (iPos == 1 && !vt && vn) { iPos = 2; // skip texture coords for normals if there are no tex coords + } if (iVal > 0) { // Store parsed index @@ -557,14 +577,17 @@ void ObjFileParser::getMaterialDesc() { // Get name std::string strName(pStart, &(*m_DataIt)); - strName = trim_whitespaces(strName); - if (strName.empty()) + strName = ai_trim(strName); + if (strName.empty()) { skip = true; + } - // If the current mesh has the same material, we simply ignore that 'usemtl' command + // If the current mesh has the same material, we will ignore that 'usemtl' command // There is no need to create another object or even mesh here - if (m_pModel->mCurrentMaterial && m_pModel->mCurrentMaterial->MaterialName == aiString(strName)) { - skip = true; + if (!skip) { + if (m_pModel->mCurrentMaterial && m_pModel->mCurrentMaterial->MaterialName == aiString(strName)) { + skip = true; + } } if (!skip) { @@ -586,7 +609,8 @@ void ObjFileParser::getMaterialDesc() { } if (needsNewMesh(strName)) { - createMesh(strName); + auto newMeshName = m_pModel->mActiveGroup.empty() ? strName : m_pModel->mActiveGroup; + createMesh(newMeshName); } m_pModel->mCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName); diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.h b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.h index 0ed724461..f3e149838 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.h +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjFileParser.h @@ -41,6 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef OBJ_FILEPARSER_H_INC #define OBJ_FILEPARSER_H_INC +#include "ObjFileData.h" + #include #include #include @@ -53,14 +55,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -namespace ObjFile { -struct Model; -struct Object; -struct Material; -struct Point3; -struct Point2; -} // namespace ObjFile - class ObjFileImporter; class IOSystem; class ProgressHandler; @@ -79,7 +73,7 @@ public: /// @brief Constructor with data array. ObjFileParser(IOStreamBuffer &streamBuffer, const std::string &modelName, IOSystem *io, ProgressHandler *progress, const std::string &originalObjFileName); /// @brief Destructor - ~ObjFileParser(); + ~ObjFileParser() = default; /// @brief If you want to load in-core data. void setBuffer(std::vector &buffer); /// @brief Model getter. @@ -149,6 +143,7 @@ private: unsigned int m_uiLine; //! Helper buffer char m_buffer[Buffersize]; + const char *mEnd; /// Pointer to IO system instance. IOSystem *m_pIO; //! Pointer to progress handler diff --git a/Engine/lib/assimp/code/AssetLib/Obj/ObjTools.h b/Engine/lib/assimp/code/AssetLib/Obj/ObjTools.h index a24bfd5a2..ac5e119f2 100644 --- a/Engine/lib/assimp/code/AssetLib/Obj/ObjTools.h +++ b/Engine/lib/assimp/code/AssetLib/Obj/ObjTools.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2021, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -/** +/** * @brief Returns true, if the last entry of the buffer is reached. * @param[in] it Iterator of current position. * @param[in] end Iterator with end of buffer. @@ -67,7 +67,7 @@ inline bool isEndOfBuffer(char_t it, char_t end) { return (it == end); } -/** +/** * @brief Returns next word separated by a space * @param[in] pBuffer Pointer to data buffer * @param[in] pEnd Pointer to end of buffer @@ -85,7 +85,7 @@ inline Char_T getNextWord(Char_T pBuffer, Char_T pEnd) { return pBuffer; } -/** +/** * @brief Returns pointer a next token * @param[in] pBuffer Pointer to data buffer * @param[in] pEnd Pointer to end of buffer @@ -102,7 +102,7 @@ inline Char_T getNextToken(Char_T pBuffer, Char_T pEnd) { return getNextWord(pBuffer, pEnd); } -/** +/** * @brief Skips a line * @param[in] it Iterator set to current position * @param[in] end Iterator set to end of scratch buffer for readout @@ -111,6 +111,10 @@ inline Char_T getNextToken(Char_T pBuffer, Char_T pEnd) { */ template inline char_t skipLine(char_t it, char_t end, unsigned int &uiLine) { + if (it >= end) { + return it; + } + while (!isEndOfBuffer(it, end) && !IsLineEnd(*it)) { ++it; } @@ -127,9 +131,9 @@ inline char_t skipLine(char_t it, char_t end, unsigned int &uiLine) { return it; } -/** +/** * @brief Get a name from the current line. Preserve space in the middle, - * but trim it at the end. + * but trim it at the end. * @param[in] it set to current position * @param[in] end set to end of scratch buffer for readout * @param[out] name Separated name @@ -158,13 +162,13 @@ inline char_t getName(char_t it, char_t end, std::string &name) { std::string strName(pStart, &(*it)); if (!strName.empty()) { name = strName; - } - + } + return it; } -/** +/** * @brief Get a name from the current line. Do not preserve space * in the middle, but trim it at the end. * @param it set to current position @@ -198,11 +202,11 @@ inline char_t getNameNoSpace(char_t it, char_t end, std::string &name) { if (!strName.empty()) { name = strName; } - + return it; } -/** +/** * @brief Get next word from given line * @param[in] it set to current position * @param[in] end set to end of scratch buffer for readout @@ -226,7 +230,7 @@ inline char_t CopyNextWord(char_t it, char_t end, char *pBuffer, size_t length) return it; } -/** +/** * @brief Get next float from given line * @param[in] it set to current position * @param[in] end set to end of scratch buffer for readout @@ -243,22 +247,6 @@ inline char_t getFloat(char_t it, char_t end, ai_real &value) { return it; } -/** - * @brief Will remove white-spaces for a string. - * @param[in] str The string to clean - * @return The trimmed string. - */ -template -inline string_type trim_whitespaces(string_type str) { - while (!str.empty() && IsSpace(str[0])) { - str.erase(0); - } - while (!str.empty() && IsSpace(str[str.length() - 1])) { - str.erase(str.length() - 1); - } - return str; -} - /** * @brief Checks for a line-end. * @param[in] it Current iterator in string. diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp b/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp index 738e1181e..ee92785ef 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h b/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h index eb026ea70..7bba8b768 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreBinarySerializer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.cpp b/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.cpp index 860aed727..8e58179bc 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -48,7 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Ogre3D Mesh Importer", "", "", diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.h b/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.h index dc8b09051..2347956f2 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.h +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,17 +60,17 @@ namespace Ogre { class OgreImporter : public BaseImporter { public: /// BaseImporter override. - virtual bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; protected: /// BaseImporter override. - virtual void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; /// BaseImporter override. - virtual const aiImporterDesc *GetInfo() const override; + const aiImporterDesc *GetInfo() const override; /// BaseImporter override. - virtual void SetupProperties(const Importer *pImp) override; + void SetupProperties(const Importer *pImp) override; private: /// Read materials referenced by the @c mesh to @c pScene. diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreMaterial.cpp b/Engine/lib/assimp/code/AssetLib/Ogre/OgreMaterial.cpp index 7478a80a9..d1244ada3 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreMaterial.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreMaterial.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -111,7 +111,7 @@ void OgreImporter::AssignMaterials(aiScene *pScene, std::vector &m aiMaterial *OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string &materialName) { if (materialName.empty()) { - return 0; + return nullptr; } // Full reference and examples of Ogre Material Script @@ -154,7 +154,7 @@ aiMaterial *OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste if (!m_userDefinedMaterialLibFile.empty()) potentialFiles.push_back(m_userDefinedMaterialLibFile); - IOStream *materialFile = 0; + IOStream *materialFile = nullptr; for (size_t i = 0; i < potentialFiles.size(); ++i) { materialFile = pIOHandler->Open(potentialFiles[i]); if (materialFile) { @@ -164,13 +164,13 @@ aiMaterial *OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste } if (!materialFile) { ASSIMP_LOG_ERROR("Failed to find source file for material '", materialName, "'"); - return 0; + return nullptr; } std::unique_ptr stream(materialFile); if (stream->FileSize() == 0) { ASSIMP_LOG_WARN("Source file for material '", materialName, "' is empty (size is 0 bytes)"); - return 0; + return nullptr; } // Read bytes diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreParsingUtils.h b/Engine/lib/assimp/code/AssetLib/Ogre/OgreParsingUtils.h index 60dd5cf76..2591644e3 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreParsingUtils.h +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreParsingUtils.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.cpp b/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.cpp index e2edc4f32..29a8d768d 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -296,7 +296,7 @@ uint32_t VertexData::VertexSize(uint16_t source) const { MemoryStream *VertexData::VertexBuffer(uint16_t source) { if (vertexBindings.find(source) != vertexBindings.end()) return vertexBindings[source].get(); - return 0; + return nullptr; } VertexElement *VertexData::GetVertexElement(VertexElement::Semantic semantic, uint16_t index) { @@ -304,7 +304,7 @@ VertexElement *VertexData::GetVertexElement(VertexElement::Semantic semantic, ui if (element.semantic == semantic && element.index == index) return &element; } - return 0; + return nullptr; } // VertexDataXml @@ -399,7 +399,7 @@ SubMesh *Mesh::GetSubMesh(size_t index) const { return subMeshes[i]; } } - return 0; + return nullptr; } void Mesh::ConvertToAssimpScene(aiScene *dest) { @@ -459,7 +459,7 @@ ISubMesh::ISubMesh() : // SubMesh SubMesh::SubMesh() : - vertexData(0), + vertexData(nullptr), indexData(new IndexData()) { } @@ -515,9 +515,9 @@ aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { // Source streams MemoryStream *positions = src->VertexBuffer(positionsElement->source); - MemoryStream *normals = (normalsElement ? src->VertexBuffer(normalsElement->source) : 0); - MemoryStream *uv1 = (uv1Element ? src->VertexBuffer(uv1Element->source) : 0); - MemoryStream *uv2 = (uv2Element ? src->VertexBuffer(uv2Element->source) : 0); + MemoryStream *normals = (normalsElement ? src->VertexBuffer(normalsElement->source) : nullptr); + MemoryStream *uv1 = (uv1Element ? src->VertexBuffer(uv1Element->source) : nullptr); + MemoryStream *uv2 = (uv2Element ? src->VertexBuffer(uv2Element->source) : nullptr); // Element size const size_t sizePosition = positionsElement->Size(); @@ -544,7 +544,7 @@ aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { dest->mTextureCoords[0] = new aiVector3D[dest->mNumVertices]; } else { ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv1Element->TypeToString(), " is not compatible with Assimp. Ignoring UV."); - uv1 = 0; + uv1 = nullptr; } } if (uv2) { @@ -553,12 +553,12 @@ aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { dest->mTextureCoords[1] = new aiVector3D[dest->mNumVertices]; } else { ASSIMP_LOG_WARN("Ogre imported UV0 type ", uv2Element->TypeToString(), " is not compatible with Assimp. Ignoring UV."); - uv2 = 0; + uv2 = nullptr; } } - aiVector3D *uv1Dest = (uv1 ? dest->mTextureCoords[0] : 0); - aiVector3D *uv2Dest = (uv2 ? dest->mTextureCoords[1] : 0); + aiVector3D *uv1Dest = (uv1 ? dest->mTextureCoords[0] : nullptr); + aiVector3D *uv2Dest = (uv2 ? dest->mTextureCoords[1] : nullptr); MemoryStream *faces = indexData->buffer.get(); for (size_t fi = 0, isize = indexData->IndexSize(), fsize = indexData->FaceSize(); @@ -640,8 +640,8 @@ aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { // MeshXml MeshXml::MeshXml() : - skeleton(0), - sharedVertexData(0) { + skeleton(nullptr), + sharedVertexData(nullptr) { } MeshXml::~MeshXml() { @@ -666,7 +666,7 @@ SubMeshXml *MeshXml::GetSubMesh(uint16_t index) const { for (size_t i = 0; i < subMeshes.size(); ++i) if (subMeshes[i]->index == index) return subMeshes[i]; - return 0; + return nullptr; } void MeshXml::ConvertToAssimpScene(aiScene *dest) { @@ -714,7 +714,7 @@ void MeshXml::ConvertToAssimpScene(aiScene *dest) { SubMeshXml::SubMeshXml() : indexData(new IndexDataXml()), - vertexData(0) { + vertexData(nullptr) { } SubMeshXml::~SubMeshXml() { @@ -827,7 +827,7 @@ Animation::Animation(Skeleton *parent) : Animation::Animation(Mesh *parent) : parentMesh(parent), - parentSkeleton(0), + parentSkeleton(nullptr), length(0.0f), baseTime(-1.0f) { // empty @@ -910,7 +910,7 @@ Bone *Skeleton::BoneByName(const std::string &name) const { if ((*iter)->name == name) return (*iter); } - return 0; + return nullptr; } Bone *Skeleton::BoneById(uint16_t id) const { @@ -918,20 +918,20 @@ Bone *Skeleton::BoneById(uint16_t id) const { if ((*iter)->id == id) return (*iter); } - return 0; + return nullptr; } // Bone Bone::Bone() : id(0), - parent(0), + parent(nullptr), parentId(-1), scale(1.0f, 1.0f, 1.0f) { } bool Bone::IsParented() const { - return (parentId != -1 && parent != 0); + return (parentId != -1 && parent != nullptr); } uint16_t Bone::ParentId() const { diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.h b/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.h index be9ffa02d..1ba8e840f 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.h +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreStructs.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -437,7 +437,7 @@ public: void CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton); /// Convert to Assimp node (animation nodes). - aiNode *ConvertToAssimpNode(Skeleton *parent, aiNode *parentNode = 0); + aiNode *ConvertToAssimpNode(Skeleton *parent, aiNode *parentNode = nullptr); /// Convert to Assimp bone (mesh bones). aiBone *ConvertToAssimpBone(Skeleton *parent, const std::vector &boneWeights); diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp b/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp index f6164c64f..2eaa74c01 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,7 +57,7 @@ namespace Assimp { namespace Ogre { //AI_WONT_RETURN void ThrowAttibuteError(const XmlParser *reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX; - +AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) AI_WONT_RETURN_SUFFIX; AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) { if (!error.empty()) { throw DeadlyImportError(error, " in node '", nodeName, "' and attribute '", name, "'"); @@ -128,7 +128,6 @@ bool OgreXmlSerializer::ReadAttribute(XmlNode &xmlNode, const char *name) } ThrowAttibuteError(xmlNode.name(), name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'"); - return false; } // Mesh XML constants @@ -490,7 +489,7 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me OgreXmlSerializer serializer(xmlParser.get()); XmlNode root = xmlParser->getRootNode(); if (std::string(root.name()) != nnSkeleton) { - printf("\nSkeleton is not a valid root: %s\n", root.name()); + ASSIMP_LOG_VERBOSE_DEBUG("nSkeleton is not a valid root: ", root.name(), "."); for (auto &a : root.children()) { if (std::string(a.name()) == nnSkeleton) { root = a; @@ -535,7 +534,7 @@ XmlParserPtr OgreXmlSerializer::OpenXmlParser(Assimp::IOSystem *pIOHandler, cons } std::unique_ptr file(pIOHandler->Open(filename)); - if (!file.get()) { + if (!file) { throw DeadlyImportError("Failed to open skeleton file ", filename); } diff --git a/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h b/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h index 406681f55..c6f8e23a2 100644 --- a/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h +++ b/Engine/lib/assimp/code/AssetLib/Ogre/OgreXmlSerializer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp index f812d0ddb..cbea5f39b 100644 --- a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h index 8e31b9ae3..93e5ffc74 100644 --- a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h +++ b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp index 2883f9612..20b2e77ac 100644 --- a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,7 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Open Game Engine Exchange", "", "", @@ -66,42 +66,42 @@ static const aiImporterDesc desc = { }; namespace Grammar { - static const char* MetricType = "Metric"; - static const char *Metric_DistanceType = "distance"; - static const char *Metric_AngleType = "angle"; - static const char *Metric_TimeType = "time"; - static const char *Metric_UpType = "up"; - static const char *NameType = "Name"; - static const char *ObjectRefType = "ObjectRef"; - static const char *MaterialRefType = "MaterialRef"; - static const char *MetricKeyType = "key"; - static const char *GeometryNodeType = "GeometryNode"; - static const char *CameraNodeType = "CameraNode"; - static const char *LightNodeType = "LightNode"; - static const char *GeometryObjectType = "GeometryObject"; - static const char *CameraObjectType = "CameraObject"; - static const char *LightObjectType = "LightObject"; - static const char *TransformType = "Transform"; - static const char *MeshType = "Mesh"; - static const char *VertexArrayType = "VertexArray"; - static const char *IndexArrayType = "IndexArray"; - static const char *MaterialType = "Material"; - static const char *ColorType = "Color"; - static const char *ParamType = "Param"; - static const char *TextureType = "Texture"; - static const char *AttenType = "Atten"; + static constexpr char MetricType[] = "Metric"; + static constexpr char Metric_DistanceType[] = "distance"; + static constexpr char Metric_AngleType[] = "angle"; + static constexpr char Metric_TimeType[] = "time"; + static constexpr char Metric_UpType[] = "up"; + static constexpr char NameType[] = "Name"; + static constexpr char ObjectRefType[] = "ObjectRef"; + static constexpr char MaterialRefType[] = "MaterialRef"; + static constexpr char MetricKeyType[] = "key"; + static constexpr char GeometryNodeType[] = "GeometryNode"; + static constexpr char CameraNodeType[] = "CameraNode"; + static constexpr char LightNodeType[] = "LightNode"; + static constexpr char GeometryObjectType[] = "GeometryObject"; + static constexpr char CameraObjectType[] = "CameraObject"; + static constexpr char LightObjectType[] = "LightObject"; + static constexpr char TransformType[] = "Transform"; + static constexpr char MeshType[] = "Mesh"; + static constexpr char VertexArrayType[] = "VertexArray"; + static constexpr char IndexArrayType[] = "IndexArray"; + static constexpr char MaterialType[] = "Material"; + static constexpr char ColorType[] = "Color"; + static constexpr char ParamType[] = "Param"; + static constexpr char TextureType[] = "Texture"; + static constexpr char AttenType[] = "Atten"; - static const char *DiffuseColorToken = "diffuse"; - static const char *SpecularColorToken = "specular"; - static const char *EmissionColorToken = "emission"; + static constexpr char DiffuseColorToken[] = "diffuse"; + static constexpr char SpecularColorToken[] = "specular"; + static constexpr char EmissionColorToken[] = "emission"; - static const char *DiffuseTextureToken = "diffuse"; - static const char *DiffuseSpecularTextureToken = "specular"; - static const char *SpecularPowerTextureToken = "specular_power"; - static const char *EmissionTextureToken = "emission"; - static const char *OpacyTextureToken = "opacity"; - static const char *TransparencyTextureToken = "transparency"; - static const char *NormalTextureToken = "normal"; + static constexpr char DiffuseTextureToken[] = "diffuse"; + static constexpr char DiffuseSpecularTextureToken[] = "specular"; + static constexpr char SpecularPowerTextureToken[] = "specular_power"; + static constexpr char EmissionTextureToken[] = "emission"; + static constexpr char OpacyTextureToken[] = "opacity"; + static constexpr char TransparencyTextureToken[] = "transparency"; + static constexpr char NormalTextureToken[] = "normal"; enum TokenType { NoneType = -1, @@ -139,7 +139,7 @@ namespace Grammar { return false; } - int idx(-1); + int idx = -1; for (size_t i = 0; i < 4; i++) { if (ValidMetricToken[i] == token) { idx = (int)i; @@ -261,7 +261,6 @@ OpenGEXImporter::RefInfo::RefInfo(aiNode *node, Type type, std::vectorgetValue()); if (nullptr != val) { if (Value::ValueType::ddl_string != val->m_type) { throw DeadlyImportError("OpenGEX: invalid data type for value in node name."); - return; } const std::string name(val->getString()); @@ -509,7 +506,6 @@ static void getRefNames(DDLNode *node, std::vector &names) { void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) { if (nullptr == m_currentNode) { throw DeadlyImportError("No parent node for name."); - return; } std::vector objRefNames; @@ -533,7 +529,6 @@ void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) { void OpenGEXImporter::handleMaterialRefNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { if (nullptr == m_currentNode) { throw DeadlyImportError("No parent node for name."); - return; } std::vector matRefNames; @@ -673,14 +668,12 @@ static void setMatrix(aiNode *node, DataArrayList *transformData) { void OpenGEXImporter::handleTransformNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { if (nullptr == m_currentNode) { throw DeadlyImportError("No parent node for name."); - return; } DataArrayList *transformData(node->getDataArrayList()); if (nullptr != transformData) { if (transformData->m_numItems != 16) { throw DeadlyImportError("Invalid number of data for transform matrix."); - return; } setMatrix(m_currentNode, transformData); } @@ -836,7 +829,6 @@ static void copyColor4DArray(size_t numItems, DataArrayList *vaList, aiColor4D * void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { if (nullptr == node) { throw DeadlyImportError("No parent node for name."); - return; } Property *prop = node->getProperties(); @@ -877,12 +869,10 @@ void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene * void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) { if (nullptr == node) { throw DeadlyImportError("No parent node for name."); - return; } if (nullptr == m_currentMesh) { throw DeadlyImportError("No current mesh for index data found."); - return; } DataArrayList *vaList = node->getDataArrayList(); diff --git a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h index 997d58af9..cf5773387 100644 --- a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h +++ b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h index 5f845483d..91b31a41a 100644 --- a/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h +++ b/Engine/lib/assimp/code/AssetLib/OpenGEX/OpenGEXStructs.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.cpp b/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.cpp index 9453e0d4d..9c4b281b8 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -346,10 +346,22 @@ void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { if (m->HasVertexColors(c)) { - mOutput.write(reinterpret_cast(&m->mColors[c][i].r), 16); + unsigned char rgba[4] = { + static_cast(m->mColors[c][i].r * 255), + static_cast(m->mColors[c][i].g * 255), + static_cast(m->mColors[c][i].b * 255), + static_cast(m->mColors[c][i].a * 255) + }; + mOutput.write(reinterpret_cast(&rgba), 4); } else { - mOutput.write(reinterpret_cast(&defaultColor.r), 16); + unsigned char rgba[4] = { + static_cast(defaultColor.r * 255), + static_cast(defaultColor.g * 255), + static_cast(defaultColor.b * 255), + static_cast(defaultColor.a * 255) + }; + mOutput.write(reinterpret_cast(&rgba), 4); } } diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.h b/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.h index ff3a54cb6..810626d69 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.cpp b/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.cpp index 97ebca1fc..5b3d3a699 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,9 +53,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace ::Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Stanford Polygon Library (PLY) Importer", "", "", @@ -71,16 +71,37 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Internal stuff namespace { -// ------------------------------------------------------------------------------------------------ -// Checks that property index is within range -template -inline const T &GetProperty(const std::vector &props, int idx) { - if (static_cast(idx) >= props.size()) { - throw DeadlyImportError("Invalid .ply file: Property index is out of range."); + // ------------------------------------------------------------------------------------------------ + // Checks that property index is within range + template + inline const T &GetProperty(const std::vector &props, int idx) { + if (static_cast(idx) >= props.size()) { + throw DeadlyImportError("Invalid .ply file: Property index is out of range."); + } + + return props[idx]; + } + + // ------------------------------------------------------------------------------------------------ + static bool isBigEndian(const char *szMe) { + ai_assert(nullptr != szMe); + + // binary_little_endian + // binary_big_endian + bool isBigEndian{ false }; +#if (defined AI_BUILD_BIG_ENDIAN) + if ('l' == *szMe || 'L' == *szMe) { + isBigEndian = true; + } +#else + if ('b' == *szMe || 'B' == *szMe) { + isBigEndian = true; + } +#endif // ! AI_BUILD_BIG_ENDIAN + + return isBigEndian; } - return props[idx]; -} } // namespace // ------------------------------------------------------------------------------------------------ @@ -93,8 +114,9 @@ PLYImporter::PLYImporter() : } // ------------------------------------------------------------------------------------------------ -// Destructor, private as well -PLYImporter::~PLYImporter() = default; +PLYImporter::~PLYImporter() { + delete mGeneratedMesh; +} // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. @@ -108,37 +130,17 @@ const aiImporterDesc *PLYImporter::GetInfo() const { return &desc; } -// ------------------------------------------------------------------------------------------------ -static bool isBigEndian(const char *szMe) { - ai_assert(nullptr != szMe); - - // binary_little_endian - // binary_big_endian - bool isBigEndian(false); -#if (defined AI_BUILD_BIG_ENDIAN) - if ('l' == *szMe || 'L' == *szMe) { - isBigEndian = true; - } -#else - if ('b' == *szMe || 'B' == *szMe) { - isBigEndian = true; - } -#endif // ! AI_BUILD_BIG_ENDIAN - - return isBigEndian; -} - // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { const std::string mode = "rb"; std::unique_ptr fileStream(pIOHandler->Open(pFile, mode)); - if (!fileStream.get()) { + if (!fileStream) { throw DeadlyImportError("Failed to open file ", pFile, "."); } // Get the file-size - const size_t fileSize(fileStream->FileSize()); + const size_t fileSize = fileStream->FileSize(); if (0 == fileSize) { throw DeadlyImportError("File ", pFile, " is empty."); } @@ -163,7 +165,8 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy mBuffer = (unsigned char *)&mBuffer2[0]; char *szMe = (char *)&this->mBuffer[0]; - SkipSpacesAndLineEnd(szMe, (const char **)&szMe); + const char *end = &mBuffer2[0] + mBuffer2.size(); + SkipSpacesAndLineEnd(szMe, (const char **)&szMe, end); // determine the format of the file data and construct the aiMesh PLY::DOM sPlyDom; @@ -171,7 +174,7 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy if (TokenMatch(szMe, "format", 6)) { if (TokenMatch(szMe, "ascii", 5)) { - SkipLine(szMe, (const char **)&szMe); + SkipLine(szMe, (const char **)&szMe, end); if (!PLY::DOM::ParseInstance(streamedBuffer, &sPlyDom, this)) { if (mGeneratedMesh != nullptr) { delete (mGeneratedMesh); @@ -183,7 +186,7 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } } else if (!::strncmp(szMe, "binary_", 7)) { szMe += 7; - const bool bIsBE(isBigEndian(szMe)); + const bool bIsBE = isBigEndian(szMe); // skip the line, parse the rest of the header and build the DOM if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE)) { @@ -215,7 +218,7 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy throw DeadlyImportError("Invalid .ply file: Missing format specification"); } - //free the file buffer + // free the file buffer streamedBuffer.close(); if (mGeneratedMesh == nullptr) { @@ -244,7 +247,9 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy // fill the mesh list pScene->mNumMeshes = 1; pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; - pScene->mMeshes[0] = mGeneratedMesh; + pScene->mMeshes[0] = mGeneratedMesh; + + // Move the mesh ownership into the scene instance mGeneratedMesh = nullptr; // generate a simple node structure @@ -257,20 +262,22 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } } +static constexpr ai_uint NotSet = 0xFFFFFFFF; + void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos) { ai_assert(nullptr != pcElement); ai_assert(nullptr != instElement); - ai_uint aiPositions[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + ai_uint aiPositions[3] = { NotSet, NotSet, NotSet }; PLY::EDataType aiTypes[3] = { EDT_Char, EDT_Char, EDT_Char }; - ai_uint aiNormal[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + ai_uint aiNormal[3] = { NotSet, NotSet, NotSet }; PLY::EDataType aiNormalTypes[3] = { EDT_Char, EDT_Char, EDT_Char }; - unsigned int aiColors[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + unsigned int aiColors[4] = { NotSet, NotSet, NotSet, NotSet }; PLY::EDataType aiColorsTypes[4] = { EDT_Char, EDT_Char, EDT_Char, EDT_Char }; - unsigned int aiTexcoord[2] = { 0xFFFFFFFF, 0xFFFFFFFF }; + unsigned int aiTexcoord[2] = { NotSet, NotSet }; PLY::EDataType aiTexcoordTypes[2] = { EDT_Char, EDT_Char }; // now check whether which normal components are available @@ -340,17 +347,17 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn if (0 != cnt) { // Position aiVector3D vOut; - if (0xFFFFFFFF != aiPositions[0]) { + if (NotSet != aiPositions[0]) { vOut.x = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiPositions[0]).avList.front(), aiTypes[0]); } - if (0xFFFFFFFF != aiPositions[1]) { + if (NotSet != aiPositions[1]) { vOut.y = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiPositions[1]).avList.front(), aiTypes[1]); } - if (0xFFFFFFFF != aiPositions[2]) { + if (NotSet != aiPositions[2]) { vOut.z = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiPositions[2]).avList.front(), aiTypes[2]); } @@ -358,28 +365,28 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn // Normals aiVector3D nOut; bool haveNormal = false; - if (0xFFFFFFFF != aiNormal[0]) { + if (NotSet != aiNormal[0]) { nOut.x = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiNormal[0]).avList.front(), aiNormalTypes[0]); haveNormal = true; } - if (0xFFFFFFFF != aiNormal[1]) { + if (NotSet != aiNormal[1]) { nOut.y = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiNormal[1]).avList.front(), aiNormalTypes[1]); haveNormal = true; } - if (0xFFFFFFFF != aiNormal[2]) { + if (NotSet != aiNormal[2]) { nOut.z = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiNormal[2]).avList.front(), aiNormalTypes[2]); haveNormal = true; } - //Colors + // Colors aiColor4D cOut; bool haveColor = false; - if (0xFFFFFFFF != aiColors[0]) { + if (NotSet != aiColors[0]) { cOut.r = NormalizeColorValue(GetProperty(instElement->alProperties, aiColors[0]) .avList.front(), @@ -387,7 +394,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn haveColor = true; } - if (0xFFFFFFFF != aiColors[1]) { + if (NotSet != aiColors[1]) { cOut.g = NormalizeColorValue(GetProperty(instElement->alProperties, aiColors[1]) .avList.front(), @@ -395,7 +402,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn haveColor = true; } - if (0xFFFFFFFF != aiColors[2]) { + if (NotSet != aiColors[2]) { cOut.b = NormalizeColorValue(GetProperty(instElement->alProperties, aiColors[2]) .avList.front(), @@ -404,7 +411,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn } // assume 1.0 for the alpha channel if it is not set - if (0xFFFFFFFF == aiColors[3]) { + if (NotSet == aiColors[3]) { cOut.a = 1.0; } else { cOut.a = NormalizeColorValue(GetProperty(instElement->alProperties, @@ -415,23 +422,23 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn haveColor = true; } - //Texture coordinates + // Texture coordinates aiVector3D tOut; tOut.z = 0; bool haveTextureCoords = false; - if (0xFFFFFFFF != aiTexcoord[0]) { + if (NotSet != aiTexcoord[0]) { tOut.x = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiTexcoord[0]).avList.front(), aiTexcoordTypes[0]); haveTextureCoords = true; } - if (0xFFFFFFFF != aiTexcoord[1]) { + if (NotSet != aiTexcoord[1]) { tOut.y = PLY::PropertyInstance::ConvertTo( GetProperty(instElement->alProperties, aiTexcoord[1]).avList.front(), aiTexcoordTypes[1]); haveTextureCoords = true; } - //create aiMesh if needed + // create aiMesh if needed if (nullptr == mGeneratedMesh) { mGeneratedMesh = new aiMesh(); mGeneratedMesh->mMaterialIndex = 0; @@ -441,6 +448,9 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn mGeneratedMesh->mNumVertices = pcElement->NumOccur; mGeneratedMesh->mVertices = new aiVector3D[mGeneratedMesh->mNumVertices]; } + if (pos >= mGeneratedMesh->mNumVertices) { + throw DeadlyImportError("Invalid .ply file: Too many vertices"); + } mGeneratedMesh->mVertices[pos] = vOut; @@ -507,16 +517,12 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst bool bOne = false; // index of the vertex index list - unsigned int iProperty = 0xFFFFFFFF; + unsigned int iProperty = NotSet; PLY::EDataType eType = EDT_Char; bool bIsTriStrip = false; - // index of the material index property - //unsigned int iMaterialIndex = 0xFFFFFFFF; - //PLY::EDataType eType2 = EDT_Char; - // texture coordinates - unsigned int iTextureCoord = 0xFFFFFFFF; + unsigned int iTextureCoord = NotSet; PLY::EDataType eType3 = EDT_Char; // face = unique number of vertex indices @@ -567,11 +573,15 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst if (mGeneratedMesh->mFaces == nullptr) { mGeneratedMesh->mNumFaces = pcElement->NumOccur; mGeneratedMesh->mFaces = new aiFace[mGeneratedMesh->mNumFaces]; + } else { + if (mGeneratedMesh->mNumFaces < pcElement->NumOccur) { + throw DeadlyImportError("Invalid .ply file: Too many faces"); + } } if (!bIsTriStrip) { // parse the list of vertex indices - if (0xFFFFFFFF != iProperty) { + if (NotSet != iProperty) { const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iProperty).avList.size(); mGeneratedMesh->mFaces[pos].mNumIndices = iNum; mGeneratedMesh->mFaces[pos].mIndices = new unsigned int[iNum]; @@ -584,18 +594,10 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst } } - // parse the material index - // cannot be handled without processing the whole file first - /*if (0xFFFFFFFF != iMaterialIndex) - { - mGeneratedMesh->mFaces[pos]. = PLY::PropertyInstance::ConvertTo( - GetProperty(instElement->alProperties, iMaterialIndex).avList.front(), eType2); - }*/ - - if (0xFFFFFFFF != iTextureCoord) { + if (NotSet != iTextureCoord) { const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iTextureCoord).avList.size(); - //should be 6 coords + // should be 6 coords std::vector::const_iterator p = GetProperty(instElement->alProperties, iTextureCoord).avList.begin(); @@ -625,7 +627,7 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst // a value of -1 indicates a restart of the strip bool flip = false; const std::vector &quak = GetProperty(instElement->alProperties, iProperty).avList; - //pvOut->reserve(pvOut->size() + quak.size() + (quak.size()>>2u)); //Limits memory consumption + // pvOut->reserve(pvOut->size() + quak.size() + (quak.size()>>2u)); //Limits memory consumption int aiTable[2] = { -1, -1 }; for (std::vector::const_iterator a = quak.begin(); a != quak.end(); ++a) { @@ -678,41 +680,29 @@ void PLYImporter::GetMaterialColor(const std::vector &avL aiColor4D *clrOut) { ai_assert(nullptr != clrOut); - if (0xFFFFFFFF == aiPositions[0]) + if (NotSet == aiPositions[0]) { clrOut->r = 0.0f; - else { - clrOut->r = NormalizeColorValue(GetProperty(avList, - aiPositions[0]) - .avList.front(), - aiTypes[0]); + } else { + clrOut->r = NormalizeColorValue(GetProperty(avList, aiPositions[0]).avList.front(), aiTypes[0]); } - if (0xFFFFFFFF == aiPositions[1]) + if (NotSet == aiPositions[1]) { clrOut->g = 0.0f; - else { - clrOut->g = NormalizeColorValue(GetProperty(avList, - aiPositions[1]) - .avList.front(), - aiTypes[1]); + } else { + clrOut->g = NormalizeColorValue(GetProperty(avList, aiPositions[1]).avList.front(), aiTypes[1]); } - if (0xFFFFFFFF == aiPositions[2]) + if (NotSet == aiPositions[2]) clrOut->b = 0.0f; else { - clrOut->b = NormalizeColorValue(GetProperty(avList, - aiPositions[2]) - .avList.front(), - aiTypes[2]); + clrOut->b = NormalizeColorValue(GetProperty(avList, aiPositions[2]).avList.front(), aiTypes[2]); } // assume 1.0 for the alpha channel ifit is not set - if (0xFFFFFFFF == aiPositions[3]) + if (NotSet == aiPositions[3]) clrOut->a = 1.0f; else { - clrOut->a = NormalizeColorValue(GetProperty(avList, - aiPositions[3]) - .avList.front(), - aiTypes[3]); + clrOut->a = NormalizeColorValue(GetProperty(avList, aiPositions[3]).avList.front(), aiTypes[3]); } } @@ -863,7 +853,7 @@ void PLYImporter::LoadMaterial(std::vector *pvOut, std::string &de const int two_sided = 1; pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); - //default texture + // default texture if (!defaultTexture.empty()) { const aiString name(defaultTexture.c_str()); pcHelper->AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0); @@ -873,7 +863,7 @@ void PLYImporter::LoadMaterial(std::vector *pvOut, std::string &de pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); } - //set to wireframe, so when using this material info we can switch to points rendering + // set to wireframe, so when using this material info we can switch to points rendering if (pointsOnly) { const int wireframe = 1; pcHelper->AddProperty(&wireframe, 1, AI_MATKEY_ENABLE_WIREFRAME); @@ -890,7 +880,7 @@ void PLYImporter::LoadMaterial(std::vector *pvOut, std::string &de int iMode = (int)aiShadingMode_Gouraud; pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - //generate white material most 3D engine just multiply ambient / diffuse color with actual ambient / light color + // generate white material most 3D engine just multiply ambient / diffuse color with actual ambient / light color aiColor3D clr; clr.b = clr.g = clr.r = 1.0f; pcHelper->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); @@ -906,13 +896,13 @@ void PLYImporter::LoadMaterial(std::vector *pvOut, std::string &de pcHelper->AddProperty(&two_sided, 1, AI_MATKEY_TWOSIDED); } - //default texture + // default texture if (!defaultTexture.empty()) { const aiString name(defaultTexture.c_str()); pcHelper->AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0); } - //set to wireframe, so when using this material info we can switch to points rendering + // set to wireframe, so when using this material info we can switch to points rendering if (pointsOnly) { const int wireframe = 1; pcHelper->AddProperty(&wireframe, 1, AI_MATKEY_ENABLE_WIREFRAME); @@ -922,4 +912,6 @@ void PLYImporter::LoadMaterial(std::vector *pvOut, std::string &de } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.h b/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.h index e29da1db6..bc9f276af 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,7 +62,7 @@ using namespace PLY; // --------------------------------------------------------------------------- /** Importer class to load the stanford PLY file format */ -class PLYImporter : public BaseImporter { +class PLYImporter final : public BaseImporter { public: PLYImporter(); ~PLYImporter() override; @@ -120,13 +120,9 @@ protected: PLY::PropertyInstance::ValueUnion val, PLY::EDataType eType); - /** Buffer to hold the loaded file */ +private: unsigned char *mBuffer; - - /** Document object model representation extracted from the file */ PLY::DOM *pcDOM; - - /** Mesh generated by loader */ aiMesh *mGeneratedMesh; }; diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.cpp b/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.cpp index df93d5a57..ffdd62efb 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -48,8 +48,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include -using namespace Assimp; +namespace Assimp { + +std::string to_string(EElementSemantic e) { + + switch (e) { + case EEST_Vertex: + return std::string{ "vertex" }; + case EEST_TriStrip: + return std::string{ "tristrips" }; + case EEST_Edge: + return std::string{ "edge" }; + case EEST_Material: + return std::string{ "material" }; + case EEST_TextureFile: + return std::string{ "TextureFile" }; + default: + return std::string{ "invalid" }; + } +} // ------------------------------------------------------------------------------------------------ PLY::EDataType PLY::Property::ParseDataType(std::vector &buffer) { @@ -280,6 +300,8 @@ bool PLY::Element::ParseElement(IOStreamBuffer &streamBuffer, std::vector< // if the exact semantic can't be determined, just store // the original string identifier pOut->szName = std::string(&buffer[0], &buffer[0] + strlen(&buffer[0])); + auto pos = pOut->szName.find_last_of(' '); + pOut->szName.erase(pos, pOut->szName.size()); } if (!PLY::DOM::SkipSpaces(buffer)) @@ -295,7 +317,7 @@ bool PLY::Element::ParseElement(IOStreamBuffer &streamBuffer, std::vector< return true; } - //parse the number of occurrences of this element + // parse the number of occurrences of this element const char *pCur = (char *)&buffer[0]; pOut->NumOccur = strtoul10(pCur, &pCur); @@ -307,8 +329,8 @@ bool PLY::Element::ParseElement(IOStreamBuffer &streamBuffer, std::vector< streamBuffer.getNextLine(buffer); pCur = (char *)&buffer[0]; - // skip all comments - PLY::DOM::SkipComments(buffer); + // skip all comments and go to next line + if (PLY::DOM::SkipComments(buffer)) continue; PLY::Property prop; if (!PLY::Property::ParseProperty(buffer, &prop)) @@ -320,13 +342,13 @@ bool PLY::Element::ParseElement(IOStreamBuffer &streamBuffer, std::vector< return true; } -// ------------------------------------------------------------------------------------------------ bool PLY::DOM::SkipSpaces(std::vector &buffer) { const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + const char *end = pCur + buffer.size(); bool ret = false; if (pCur) { const char *szCur = pCur; - ret = Assimp::SkipSpaces(pCur, &pCur); + ret = Assimp::SkipSpaces(pCur, &pCur, end); uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; buffer.erase(buffer.begin(), buffer.begin() + iDiff); @@ -338,10 +360,11 @@ bool PLY::DOM::SkipSpaces(std::vector &buffer) { bool PLY::DOM::SkipLine(std::vector &buffer) { const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + const char *end = pCur + buffer.size(); bool ret = false; if (pCur) { const char *szCur = pCur; - ret = Assimp::SkipLine(pCur, &pCur); + ret = Assimp::SkipLine(pCur, &pCur, end); uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; buffer.erase(buffer.begin(), buffer.begin() + iDiff); @@ -368,10 +391,11 @@ bool PLY::DOM::TokenMatch(std::vector &buffer, const char *token, unsigned bool PLY::DOM::SkipSpacesAndLineEnd(std::vector &buffer) { const char *pCur = buffer.empty() ? nullptr : (char *)&buffer[0]; + const char *end = pCur + buffer.size(); bool ret = false; if (pCur) { const char *szCur = pCur; - ret = Assimp::SkipSpacesAndLineEnd(pCur, &pCur); + ret = Assimp::SkipSpacesAndLineEnd(pCur, &pCur, end); uintptr_t iDiff = (uintptr_t)pCur - (uintptr_t)szCur; buffer.erase(buffer.begin(), buffer.begin() + iDiff); @@ -381,10 +405,10 @@ bool PLY::DOM::SkipSpacesAndLineEnd(std::vector &buffer) { return ret; } -bool PLY::DOM::SkipComments(std::vector &buffer) { +bool PLY::DOM::SkipComments(std::vector buffer) { ai_assert(!buffer.empty()); - std::vector nbuffer = buffer; + std::vector nbuffer = std::move(buffer); // skip spaces if (!SkipSpaces(nbuffer)) { return false; @@ -410,6 +434,7 @@ bool PLY::DOM::SkipComments(std::vector &buffer) { bool PLY::DOM::ParseHeader(IOStreamBuffer &streamBuffer, std::vector &buffer, bool isBinary) { ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() begin"); + std::unordered_set definedAlElements; // parse all elements while (!buffer.empty()) { // skip all comments @@ -418,13 +443,21 @@ bool PLY::DOM::ParseHeader(IOStreamBuffer &streamBuffer, std::vector PLY::Element out; if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) { // add the element to the list of elements + + const auto propertyName = (out.szName.empty()) ? to_string(out.eSemantic) : out.szName; + auto alreadyDefined = definedAlElements.find(propertyName); + if (alreadyDefined != definedAlElements.end()) { + throw DeadlyImportError("Property '" + propertyName + "' in header already defined "); + } + definedAlElements.insert(propertyName); alElements.push_back(out); - } else if (TokenMatch(buffer, "end_header", 10)) { //checks for /n ending, if it doesn't end with /r/n + } else if (TokenMatch(buffer, "end_header", 10)) { // we have reached the end of the header break; } else { // ignore unknown header elements - streamBuffer.getNextLine(buffer); + if (!streamBuffer.getNextLine(buffer)) + return false; } } @@ -444,7 +477,7 @@ bool PLY::DOM::ParseElementInstanceLists(IOStreamBuffer &streamBuffer, std std::vector::iterator a = alElementData.begin(); // parse all element instances - //construct vertices and faces + // construct vertices and faces for (; i != alElements.end(); ++i, ++a) { if ((*i).eSemantic == EEST_Vertex || (*i).eSemantic == EEST_Face || (*i).eSemantic == EEST_TriStrip) { PLY::ElementInstanceList::ParseInstanceList(streamBuffer, buffer, &(*i), nullptr, loader); @@ -501,10 +534,6 @@ bool PLY::DOM::ParseInstanceBinary(IOStreamBuffer &streamBuffer, DOM *p_pc streamBuffer.getNextBlock(buffer); - // remove first char if it's /n in case of file with /r/n - if (((char *)&buffer[0])[0] == '\n') - buffer.erase(buffer.begin(), buffer.begin() + 1); - unsigned int bufferSize = static_cast(buffer.size()); const char *pCur = (char *)&buffer[0]; if (!p_pcOut->ParseElementInstanceListsBinary(streamBuffer, buffer, pCur, bufferSize, loader, p_bBE)) { @@ -530,7 +559,7 @@ bool PLY::DOM::ParseInstance(IOStreamBuffer &streamBuffer, DOM *p_pcOut, P return false; } - //get next line after header + // get next line after header streamBuffer.getNextLine(buffer); if (!p_pcOut->ParseElementInstanceLists(streamBuffer, buffer, loader)) { ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseInstance() failure"); @@ -560,23 +589,24 @@ bool PLY::ElementInstanceList::ParseInstanceList( } } else { const char *pCur = (const char *)&buffer[0]; + const char *end = pCur + buffer.size(); // be sure to have enough storage for (unsigned int i = 0; i < pcElement->NumOccur; ++i) { if (p_pcOut) - PLY::ElementInstance::ParseInstance(pCur, pcElement, &p_pcOut->alInstances[i]); + PLY::ElementInstance::ParseInstance(pCur, end, pcElement, &p_pcOut->alInstances[i]); else { ElementInstance elt; - PLY::ElementInstance::ParseInstance(pCur, pcElement, &elt); + PLY::ElementInstance::ParseInstance(pCur, end, pcElement, &elt); // Create vertex or face if (pcElement->eSemantic == EEST_Vertex) { - //call loader instance from here + // call loader instance from here loader->LoadVertex(pcElement, &elt, i); } else if (pcElement->eSemantic == EEST_Face) { - //call loader instance from here + // call loader instance from here loader->LoadFace(pcElement, &elt, i); } else if (pcElement->eSemantic == EEST_TriStrip) { - //call loader instance from here + // call loader instance from here loader->LoadFace(pcElement, &elt, i); } } @@ -613,13 +643,13 @@ bool PLY::ElementInstanceList::ParseInstanceListBinary( // Create vertex or face if (pcElement->eSemantic == EEST_Vertex) { - //call loader instance from here + // call loader instance from here loader->LoadVertex(pcElement, &elt, i); } else if (pcElement->eSemantic == EEST_Face) { - //call loader instance from here + // call loader instance from here loader->LoadFace(pcElement, &elt, i); } else if (pcElement->eSemantic == EEST_TriStrip) { - //call loader instance from here + // call loader instance from here loader->LoadFace(pcElement, &elt, i); } } @@ -628,7 +658,7 @@ bool PLY::ElementInstanceList::ParseInstanceListBinary( } // ------------------------------------------------------------------------------------------------ -bool PLY::ElementInstance::ParseInstance(const char *&pCur, +bool PLY::ElementInstance::ParseInstance(const char *&pCur, const char *end, const PLY::Element *pcElement, PLY::ElementInstance *p_pcOut) { ai_assert(nullptr != pcElement); @@ -640,7 +670,7 @@ bool PLY::ElementInstance::ParseInstance(const char *&pCur, std::vector::iterator i = p_pcOut->alProperties.begin(); std::vector::const_iterator a = pcElement->alProperties.begin(); for (; i != p_pcOut->alProperties.end(); ++i, ++a) { - if (!(PLY::PropertyInstance::ParseInstance(pCur, &(*a), &(*i)))) { + if (!(PLY::PropertyInstance::ParseInstance(pCur, end, &(*a), &(*i)))) { ASSIMP_LOG_WARN("Unable to parse property instance. " "Skipping this element instance"); @@ -680,13 +710,13 @@ bool PLY::ElementInstance::ParseInstanceBinary( } // ------------------------------------------------------------------------------------------------ -bool PLY::PropertyInstance::ParseInstance(const char *&pCur, - const PLY::Property *prop, PLY::PropertyInstance *p_pcOut) { +bool PLY::PropertyInstance::ParseInstance(const char *&pCur, const char *end, const PLY::Property *prop, + PLY::PropertyInstance *p_pcOut) { ai_assert(nullptr != prop); ai_assert(nullptr != p_pcOut); // skip spaces at the beginning - if (!SkipSpaces(&pCur)) { + if (!SkipSpaces(&pCur, end)) { return false; } @@ -701,7 +731,7 @@ bool PLY::PropertyInstance::ParseInstance(const char *&pCur, // parse all list elements p_pcOut->avList.resize(iNum); for (unsigned int i = 0; i < iNum; ++i) { - if (!SkipSpaces(&pCur)) + if (!SkipSpaces(&pCur, end)) return false; PLY::PropertyInstance::ParseValue(pCur, prop->eType, &p_pcOut->avList[i]); @@ -713,7 +743,7 @@ bool PLY::PropertyInstance::ParseInstance(const char *&pCur, PLY::PropertyInstance::ParseValue(pCur, prop->eType, &v); p_pcOut->avList.push_back(v); } - SkipSpacesAndLineEnd(&pCur); + SkipSpacesAndLineEnd(&pCur, end); return true; } @@ -776,7 +806,7 @@ bool PLY::PropertyInstance::ParseValue(const char *&pCur, ai_assert(nullptr != pCur); ai_assert(nullptr != out); - //calc element size + // calc element size bool ret = true; switch (eType) { case EDT_UInt: @@ -826,7 +856,7 @@ bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer &streamBuffer, bool p_bBE) { ai_assert(nullptr != out); - //calc element size + // calc element size unsigned int lsize = 0; switch (eType) { case EDT_Char: @@ -854,11 +884,11 @@ bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer &streamBuffer, break; } - //read the next file block if needed + // read the next file block if needed if (bufferSize < lsize) { std::vector nbuffer; if (streamBuffer.getNextBlock(nbuffer)) { - //concat buffer contents + // concat buffer contents buffer = std::vector(buffer.end() - bufferSize, buffer.end()); buffer.insert(buffer.end(), nbuffer.begin(), nbuffer.end()); nbuffer.clear(); @@ -960,4 +990,6 @@ bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer &streamBuffer, return ret; } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_PLY_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.h b/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.h index 86349294e..fc6f346af 100644 --- a/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.h +++ b/Engine/lib/assimp/code/AssetLib/Ply/PlyParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -296,9 +296,7 @@ class PropertyInstance public: //! Default constructor - PropertyInstance() AI_NO_EXCEPT { - // empty - } + PropertyInstance() AI_NO_EXCEPT = default; union ValueUnion { @@ -326,7 +324,7 @@ public: // ------------------------------------------------------------------- //! Parse a property instance - static bool ParseInstance(const char* &pCur, + static bool ParseInstance(const char* &pCur, const char *end, const Property* prop, PropertyInstance* p_pcOut); // ------------------------------------------------------------------- @@ -359,17 +357,14 @@ public: class ElementInstance { public: //! Default constructor - ElementInstance() AI_NO_EXCEPT - : alProperties() { - // empty - } + ElementInstance() AI_NO_EXCEPT = default; //! List of all parsed properties std::vector< PropertyInstance > alProperties; // ------------------------------------------------------------------- //! Parse an element instance - static bool ParseInstance(const char* &pCur, + static bool ParseInstance(const char *&pCur, const char *end, const Element* pcElement, ElementInstance* p_pcOut); // ------------------------------------------------------------------- @@ -386,10 +381,7 @@ class ElementInstanceList public: //! Default constructor - ElementInstanceList() AI_NO_EXCEPT - : alInstances() { - // empty - } + ElementInstanceList() AI_NO_EXCEPT = default; //! List of all element instances std::vector< ElementInstance > alInstances; @@ -413,11 +405,7 @@ class DOM public: //! Default constructor - DOM() AI_NO_EXCEPT - : alElements() - , alElementData() { - - } + DOM() AI_NO_EXCEPT = default; //! Contains all elements of the file format @@ -431,7 +419,7 @@ public: static bool ParseInstanceBinary(IOStreamBuffer &streamBuffer, DOM* p_pcOut, PLYImporter* loader, bool p_bBE); //! Skip all comment lines after this - static bool SkipComments(std::vector &buffer); + static bool SkipComments(std::vector buffer); static bool SkipSpaces(std::vector &buffer); diff --git a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h index 8ccee0b0a..2d86ce564 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h +++ b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileData.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -169,19 +169,7 @@ struct Q3BSPModel { std::vector m_EntityData; std::string m_ModelName; - Q3BSPModel() : - m_Data(), - m_Lumps(), - m_Vertices(), - m_Faces(), - m_Indices(), - m_Textures(), - m_Lightmaps(), - m_EntityData(), - m_ModelName() - { - // empty - } + Q3BSPModel() = default; ~Q3BSPModel() { for ( unsigned int i=0; i -#ifdef ASSIMP_BUILD_NO_OWN_ZLIB -#include -#else -#include "../contrib/zlib/zlib.h" -#endif +#include "zlib.h" #include #include @@ -65,7 +61,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Quake III BSP Importer", "", "", @@ -139,14 +135,18 @@ static void normalizePathName(const std::string &rPath, std::string &normalizedP // ------------------------------------------------------------------------------------------------ // Constructor. Q3BSPFileImporter::Q3BSPFileImporter() : - m_pCurrentMesh(nullptr), m_pCurrentFace(nullptr), m_MaterialLookupMap(), mTextures() { + m_pCurrentMesh(nullptr), m_pCurrentFace(nullptr) { // empty } // ------------------------------------------------------------------------------------------------ // Destructor. Q3BSPFileImporter::~Q3BSPFileImporter() { - // Clear face-to-material map + clear(); +} + +// ------------------------------------------------------------------------------------------------ +void Q3BSPFileImporter::clear() { for (FaceMap::iterator it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); ++it) { const std::string &matName = it->first; if (!matName.empty()) { @@ -173,6 +173,7 @@ const aiImporterDesc *Q3BSPFileImporter::GetInfo() const { // ------------------------------------------------------------------------------------------------ // Import method. void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene *scene, IOSystem *ioHandler) { + clear(); ZipArchiveIOSystem Archive(ioHandler, rFile); if (!Archive.isOpen()) { throw DeadlyImportError("Failed to open file ", rFile, "."); @@ -394,7 +395,10 @@ void Q3BSPFileImporter::createTriangleTopology(const Q3BSP::Q3BSPModel *pModel, m_pCurrentFace->mIndices = new unsigned int[3]; m_pCurrentFace->mIndices[idx] = vertIdx; } - } + } else { + m_pCurrentFace->mIndices[idx] = vertIdx; + } + pMesh->mVertices[vertIdx].Set(pVertex->vPosition.x, pVertex->vPosition.y, pVertex->vPosition.z); pMesh->mNormals[vertIdx].Set(pVertex->vNormal.x, pVertex->vNormal.y, pVertex->vNormal.z); @@ -584,7 +588,7 @@ bool Q3BSPFileImporter::importTextureFromArchive(const Q3BSP::Q3BSPModel *model, aiString name; name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(mTextures.size())); archive->Close(pTextureStream); @@ -637,7 +641,7 @@ bool Q3BSPFileImporter::importLightmap(const Q3BSP::Q3BSPModel *pModel, aiScene aiString name; name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(mTextures.size())); pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_LIGHTMAP(1)); mTextures.push_back(pTexture); diff --git a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h index fdcfff876..b779c55a5 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h +++ b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -81,6 +81,7 @@ protected: using FaceMapIt = std::map* >::iterator; using FaceMapConstIt = std::map*>::const_iterator; + void clear(); const aiImporterDesc* GetInfo () const override; void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; void separateMapName( const std::string &rImportName, std::string &rArchiveName, std::string &rMapName ); diff --git a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp index 910da5b36..c9c3d24cf 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp +++ b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h index 15cc751cc..de4d609e4 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h +++ b/Engine/lib/assimp/code/AssetLib/Q3BSP/Q3BSPFileParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.cpp b/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.cpp index a91788c78..84a508979 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,9 +55,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Quick3D Importer", "", "", @@ -127,7 +127,7 @@ void Q3DImporter::InternReadFile(const std::string &pFile, std::vector materials; try { materials.reserve(numMats); - } catch(const std::bad_alloc&) { + } catch (const std::bad_alloc &) { ASSIMP_LOG_ERROR("Invalid alloc for materials."); throw DeadlyImportError("Invalid Quick3D-file, material allocation failed."); } @@ -135,7 +135,7 @@ void Q3DImporter::InternReadFile(const std::string &pFile, std::vector meshes; try { meshes.reserve(numMeshes); - } catch(const std::bad_alloc&) { + } catch (const std::bad_alloc &) { ASSIMP_LOG_ERROR("Invalid alloc for meshes."); throw DeadlyImportError("Invalid Quick3D-file, mesh allocation failed."); } @@ -237,7 +237,6 @@ void Q3DImporter::InternReadFile(const std::string &pFile, if (minor > '0' && major == '3') stream.IncPtr(mesh.faces.size()); } - // stream.IncPtr(4); // unknown value here } break; // materials chunk @@ -251,6 +250,10 @@ void Q3DImporter::InternReadFile(const std::string &pFile, c = stream.GetI1(); while (c) { mat.name.data[mat.name.length++] = c; + if (mat.name.length == AI_MAXLEN) { + ASSIMP_LOG_ERROR("String ouverflow detected, skipped material name parsing."); + break; + } c = stream.GetI1(); } @@ -275,8 +278,6 @@ void Q3DImporter::InternReadFile(const std::string &pFile, // read the transparency mat.transparency = stream.GetF4(); - // unknown value here - // stream.IncPtr(4); // FIX: it could be the texture index ... mat.texIdx = (unsigned int)stream.GetI4(); } @@ -382,11 +383,10 @@ void Q3DImporter::InternReadFile(const std::string &pFile, // TODO goto outer; - } break; + } default: throw DeadlyImportError("Quick3D: Unknown chunk"); - break; }; } outer: @@ -426,7 +426,8 @@ outer: pScene->mMeshes = new aiMesh *[pScene->mNumMaterials]; for (unsigned int i = 0, real = 0; i < (unsigned int)materials.size(); ++i) { - if (fidx[i].empty()) continue; + if (fidx[i].empty()) + continue; // Allocate a mesh and a material aiMesh *mesh = pScene->mMeshes[real] = new aiMesh(); @@ -549,14 +550,9 @@ outer: // Now we need to attach the meshes to the root node of the scene pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; - for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { pScene->mRootNode->mMeshes[i] = i; - - /*pScene->mRootNode->mTransformation *= aiMatrix4x4( - 1.f, 0.f, 0.f, 0.f, - 0.f, -1.f,0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, - 0.f, 0.f, 0.f, 1.f);*/ + } // Add cameras and light sources to the scene root node pScene->mRootNode->mNumChildren = pScene->mNumLights + pScene->mNumCameras; @@ -578,4 +574,6 @@ outer: } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_Q3D_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.h b/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.h index 54af86dc2..ed33ed7ca 100644 --- a/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Q3D/Q3DLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.cpp b/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.cpp index 41a8f14db..6c04680b8 100644 --- a/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,9 +55,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Raw Importer", "", "", @@ -70,14 +70,6 @@ static const aiImporterDesc desc = { "raw" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -RAWImporter::RAWImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -RAWImporter::~RAWImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool RAWImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { @@ -96,7 +88,7 @@ void RAWImporter::InternReadFile(const std::string &pFile, std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open RAW file ", pFile, "."); } @@ -112,11 +104,12 @@ void RAWImporter::InternReadFile(const std::string &pFile, // now read all lines char line[4096]; + const char *end = &line[4096]; while (GetNextLine(buffer, line)) { // if the line starts with a non-numeric identifier, it marks // the beginning of a new group const char *sz = line; - SkipSpaces(&sz); + SkipSpaces(&sz, end); if (IsLineEnd(*sz)) continue; if (!IsNumeric(*sz)) { const char *sz2 = sz; @@ -125,8 +118,8 @@ void RAWImporter::InternReadFile(const std::string &pFile, const unsigned int length = (unsigned int)(sz2 - sz); // find an existing group with this name - for (std::vector::iterator it = outGroups.begin(), end = outGroups.end(); - it != end; ++it) { + for (std::vector::iterator it = outGroups.begin(), endIt = outGroups.end(); + it != endIt; ++it) { if (length == (*it).name.length() && !::strcmp(sz, (*it).name.c_str())) { curGroup = it; sz2 = nullptr; @@ -142,7 +135,7 @@ void RAWImporter::InternReadFile(const std::string &pFile, float data[12]; unsigned int num; for (num = 0; num < 12; ++num) { - if (!SkipSpaces(&sz) || !IsNumeric(*sz)) break; + if (!SkipSpaces(&sz, end) || !IsNumeric(*sz)) break; sz = fast_atoreal_move(sz, data[num]); } if (num != 12 && num != 9) { @@ -295,4 +288,6 @@ void RAWImporter::InternReadFile(const std::string &pFile, } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_RAW_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.h b/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.h index 6e4c4d110..aa0fbdf81 100644 --- a/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Raw/RawLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,8 +57,8 @@ namespace Assimp { */ class RAWImporter : public BaseImporter { public: - RAWImporter(); - ~RAWImporter(); + RAWImporter() = default; + ~RAWImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.cpp b/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.cpp index 323a69a00..e55e67541 100644 --- a/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,11 +56,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#ifdef ASSIMP_USE_HUNTER -#include -#else -#include "../contrib/utf8cpp/source/utf8.h" -#endif +#include "utf8.h" #include #include #include @@ -69,9 +65,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Silo SIB Importer", "Richard Mitton (http://www.codersnotes.com/about)", "", @@ -85,7 +81,7 @@ static const aiImporterDesc desc = { struct SIBChunk { uint32_t Tag; uint32_t Size; -} PACK_STRUCT; +}; enum { POS, @@ -94,7 +90,7 @@ enum { N }; -typedef std::pair SIBPair; +using SIBPair = std::pair; struct SIBEdge { uint32_t faceA, faceB; @@ -199,15 +195,6 @@ static aiString ReadString(StreamReaderLE *stream, uint32_t numWChars) { return result; } - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -SIBImporter::SIBImporter() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -SIBImporter::~SIBImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool SIBImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { @@ -882,4 +869,6 @@ void SIBImporter::InternReadFile(const std::string &pFile, } } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_SIB_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.h b/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.h index 2b197ddca..9dd0c0095 100644 --- a/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.h +++ b/Engine/lib/assimp/code/AssetLib/SIB/SIBImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,8 +57,8 @@ namespace Assimp { */ class ASSIMP_API SIBImporter : public BaseImporter { public: - SIBImporter(); - ~SIBImporter() override; + SIBImporter() = default; + ~SIBImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. diff --git a/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.cpp b/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.cpp index cbbc894ad..a70320276 100644 --- a/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -64,9 +64,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define strtok_s strtok_r #endif -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Valve SMD Importer", "", "", @@ -82,20 +82,18 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer SMDImporter::SMDImporter() : - configFrameID(), - mBuffer(), - pScene( nullptr ), + configFrameID(), + mBuffer(), + mEnd(nullptr), + pScene(nullptr), iFileSize( 0 ), iSmallestFrame( INT_MAX ), dLengthOfAnim( 0.0 ), - bHasUVs(false ), + bHasUVs(false ), iLineNumber((unsigned int)-1) { // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -SMDImporter::~SMDImporter() = default; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. @@ -402,8 +400,12 @@ void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent) { } } + // nothing to do + if (pcNode->mNumChildren == 0) + return; + // now allocate the output array - pcNode->mChildren = new aiNode*[pcNode->mNumChildren]; + pcNode->mChildren = new aiNode *[pcNode->mNumChildren]; // and fill all subnodes unsigned int qq( 0 ); @@ -453,11 +455,10 @@ void SMDImporter::CreateOutputNodes() { delete pcOldRoot; pScene->mRootNode->mParent = nullptr; - } - else - { - ::strcpy(pScene->mRootNode->mName.data, ""); + } else { + static constexpr char rootName[11] = ""; pScene->mRootNode->mName.length = 10; + ::strncpy(pScene->mRootNode->mName.data, rootName, pScene->mRootNode->mName.length); } } @@ -535,7 +536,7 @@ void SMDImporter::GetAnimationFileList(const std::string &pFile, IOSystem* pIOHa auto path = base + "/" + name + "_animation.txt"; std::unique_ptr file(pIOHandler->Open(path.c_str(), "rb")); - if (file.get() == nullptr) { + if (file == nullptr) { return; } @@ -591,12 +592,12 @@ void SMDImporter::CreateOutputMaterials() { pScene->mMaterials[iMat] = pcMat; aiString szName; - szName.length = (size_t)ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat); + szName.length = static_cast(ai_snprintf(szName.data, AI_MAXLEN, "Texture_%u", iMat)); pcMat->AddProperty(&szName,AI_MATKEY_NAME); if (aszTextures[iMat].length()) { - ::strncpy(szName.data, aszTextures[iMat].c_str(),MAXLEN-1); + ::strncpy(szName.data, aszTextures[iMat].c_str(), AI_MAXLEN - 1); szName.length = static_cast( aszTextures[iMat].length() ); pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0)); } @@ -633,13 +634,13 @@ void SMDImporter::ParseFile() { // read line per line ... for ( ;; ) { - if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent, mEnd)) { break; } // "version \n", should be 1 for hl and hl2 SMD files if (TokenMatch(szCurrent,"version",7)) { - if(!SkipSpaces(szCurrent,&szCurrent)) break; + if(!SkipSpaces(szCurrent,&szCurrent, mEnd)) break; if (1 != strtoul10(szCurrent,&szCurrent)) { ASSIMP_LOG_WARN("SMD.version is not 1. This " "file format is not known. Continuing happily ..."); @@ -648,26 +649,26 @@ void SMDImporter::ParseFile() { } // "nodes\n" - Starts the node section if (TokenMatch(szCurrent,"nodes",5)) { - ParseNodesSection(szCurrent,&szCurrent); + ParseNodesSection(szCurrent, &szCurrent, mEnd); continue; } // "triangles\n" - Starts the triangle section if (TokenMatch(szCurrent,"triangles",9)) { - ParseTrianglesSection(szCurrent,&szCurrent); + ParseTrianglesSection(szCurrent, &szCurrent, mEnd); continue; } // "vertexanimation\n" - Starts the vertex animation section if (TokenMatch(szCurrent,"vertexanimation",15)) { bHasUVs = false; - ParseVASection(szCurrent,&szCurrent); + ParseVASection(szCurrent, &szCurrent, mEnd); continue; } // "skeleton\n" - Starts the skeleton section if (TokenMatch(szCurrent,"skeleton",8)) { - ParseSkeletonSection(szCurrent,&szCurrent); + ParseSkeletonSection(szCurrent, &szCurrent, mEnd); continue; } - SkipLine(szCurrent,&szCurrent); + SkipLine(szCurrent, &szCurrent, mEnd); } } @@ -675,7 +676,7 @@ void SMDImporter::ReadSmd(const std::string &pFile, IOSystem* pIOHandler) { std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open SMD/VTA file ", pFile, "."); } @@ -684,6 +685,7 @@ void SMDImporter::ReadSmd(const std::string &pFile, IOSystem* pIOHandler) { // Allocate storage and copy the contents of the file to a memory buffer mBuffer.resize(iFileSize + 1); TextFileToBuffer(file.get(), mBuffer); + mEnd = &mBuffer[mBuffer.size() - 1] + 1; iSmallestFrame = INT_MAX; bHasUVs = true; @@ -724,26 +726,26 @@ unsigned int SMDImporter::GetTextureIndex(const std::string& filename) { // ------------------------------------------------------------------------------------------------ // Parse the nodes section of the file -void SMDImporter::ParseNodesSection(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseNodesSection(const char* szCurrent, const char** szCurrentOut, const char *end) { for ( ;; ) { // "end\n" - Ends the nodes section - if (0 == ASSIMP_strincmp(szCurrent,"end",3) && IsSpaceOrNewLine(*(szCurrent+3))) { + if (0 == ASSIMP_strincmp(szCurrent, "end", 3) && IsSpaceOrNewLine(*(szCurrent+3))) { szCurrent += 4; break; } - ParseNodeInfo(szCurrent,&szCurrent); + ParseNodeInfo(szCurrent,&szCurrent, end); } - SkipSpacesAndLineEnd(szCurrent,&szCurrent); + SkipSpacesAndLineEnd(szCurrent, &szCurrent, end); *szCurrentOut = szCurrent; } // ------------------------------------------------------------------------------------------------ // Parse the triangles section of the file -void SMDImporter::ParseTrianglesSection(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseTrianglesSection(const char *szCurrent, const char **szCurrentOut, const char *end) { // Parse a triangle, parse another triangle, parse the next triangle ... // and so on until we reach a token that looks quite similar to "end" for ( ;; ) { - if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent, end)) { break; } @@ -751,17 +753,17 @@ void SMDImporter::ParseTrianglesSection(const char* szCurrent, const char** szCu if (TokenMatch(szCurrent,"end",3)) { break; } - ParseTriangle(szCurrent,&szCurrent); + ParseTriangle(szCurrent,&szCurrent, end); } - SkipSpacesAndLineEnd(szCurrent,&szCurrent); + SkipSpacesAndLineEnd(szCurrent,&szCurrent, end); *szCurrentOut = szCurrent; } // ------------------------------------------------------------------------------------------------ // Parse the vertex animation section of the file -void SMDImporter::ParseVASection(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseVASection(const char *szCurrent, const char **szCurrentOut, const char *end) { unsigned int iCurIndex = 0; for ( ;; ) { - if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent, end)) { break; } @@ -775,10 +777,10 @@ void SMDImporter::ParseVASection(const char* szCurrent, const char** szCurrentOu // NOTE: The doc says that time values COULD be negative ... // NOTE2: this is the shape key -> valve docs int iTime = 0; - if(!ParseSignedInt(szCurrent,&szCurrent,iTime) || configFrameID != (unsigned int)iTime) { + if (!ParseSignedInt(szCurrent, &szCurrent, end, iTime) || configFrameID != (unsigned int)iTime) { break; } - SkipLine(szCurrent,&szCurrent); + SkipLine(szCurrent,&szCurrent, end); } else { if(0 == iCurIndex) { asTriangles.emplace_back(); @@ -786,7 +788,7 @@ void SMDImporter::ParseVASection(const char* szCurrent, const char** szCurrentOu if (++iCurIndex == 3) { iCurIndex = 0; } - ParseVertex(szCurrent,&szCurrent,asTriangles.back().avVertices[iCurIndex],true); + ParseVertex(szCurrent,&szCurrent, end, asTriangles.back().avVertices[iCurIndex],true); } } @@ -795,16 +797,16 @@ void SMDImporter::ParseVASection(const char* szCurrent, const char** szCurrentOu asTriangles.pop_back(); } - SkipSpacesAndLineEnd(szCurrent,&szCurrent); + SkipSpacesAndLineEnd(szCurrent,&szCurrent, end); *szCurrentOut = szCurrent; } // ------------------------------------------------------------------------------------------------ // Parse the skeleton section of the file -void SMDImporter::ParseSkeletonSection(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseSkeletonSection(const char *szCurrent, const char **szCurrentOut, const char *end) { int iTime = 0; for ( ;; ) { - if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) { + if (!SkipSpacesAndLineEnd(szCurrent,&szCurrent, end)) { break; } @@ -812,15 +814,15 @@ void SMDImporter::ParseSkeletonSection(const char* szCurrent, const char** szCur if (TokenMatch(szCurrent,"end",3)) { break; } else if (TokenMatch(szCurrent,"time",4)) { - // "time \n" - Specifies the current animation frame - if(!ParseSignedInt(szCurrent,&szCurrent,iTime)) { + // "time \n" - Specifies the current animation frame + if (!ParseSignedInt(szCurrent, &szCurrent, end, iTime)) { break; } iSmallestFrame = std::min(iSmallestFrame,iTime); - SkipLine(szCurrent,&szCurrent); + SkipLine(szCurrent, &szCurrent, end); } else { - ParseSkeletonElement(szCurrent,&szCurrent,iTime); + ParseSkeletonElement(szCurrent, &szCurrent, end, iTime); } } *szCurrentOut = szCurrent; @@ -828,17 +830,20 @@ void SMDImporter::ParseSkeletonSection(const char* szCurrent, const char** szCur // ------------------------------------------------------------------------------------------------ #define SMDI_PARSE_RETURN { \ - SkipLine(szCurrent,&szCurrent); \ + SkipLine(szCurrent,&szCurrent, end); \ *szCurrentOut = szCurrent; \ return; \ } // ------------------------------------------------------------------------------------------------ // Parse a node line -void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseNodeInfo(const char *szCurrent, const char **szCurrentOut, const char *end) { unsigned int iBone = 0; - SkipSpacesAndLineEnd(szCurrent,&szCurrent); - if ( !ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent)) { - LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index"); + SkipSpacesAndLineEnd(szCurrent, &szCurrent, end); + if ( !ParseUnsignedInt(szCurrent, &szCurrent, end, iBone) || !SkipSpaces(szCurrent,&szCurrent, end)) { + throw DeadlyImportError("Unexpected EOF/EOL while parsing bone index"); + } + if (iBone == UINT_MAX) { + LogErrorNoThrow("Invalid bone number while parsing bone index"); SMDI_PARSE_RETURN; } // add our bone to the list @@ -875,7 +880,7 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut szCurrent = szEnd; // the only negative bone parent index that could occur is -1 AFAIK - if(!ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent)) { + if(!ParseSignedInt(szCurrent, &szCurrent, end, (int&)bone.iParent)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone parent index. Assuming -1"); SMDI_PARSE_RETURN; } @@ -886,12 +891,12 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut // ------------------------------------------------------------------------------------------------ // Parse a skeleton element -void SMDImporter::ParseSkeletonElement(const char* szCurrent, const char** szCurrentOut,int iTime) { +void SMDImporter::ParseSkeletonElement(const char *szCurrent, const char **szCurrentOut, const char *end, int iTime) { aiVector3D vPos; aiVector3D vRot; unsigned int iBone = 0; - if(!ParseUnsignedInt(szCurrent,&szCurrent,iBone)) { + if (!ParseUnsignedInt(szCurrent, &szCurrent, end, iBone)) { ASSIMP_LOG_ERROR("Unexpected EOF/EOL while parsing bone index"); SMDI_PARSE_RETURN; } @@ -905,27 +910,27 @@ void SMDImporter::ParseSkeletonElement(const char* szCurrent, const char** szCur SMD::Bone::Animation::MatrixKey& key = bone.sAnim.asKeys.back(); key.dTime = (double)iTime; - if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.x)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vPos.x)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.x"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.y)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vPos.y)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.y"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.z)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vPos.z)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.z"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.x)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vRot.x)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.x"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.y)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vRot.y)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.y"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.z)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vRot.z)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.z"); SMDI_PARSE_RETURN; } @@ -945,11 +950,11 @@ void SMDImporter::ParseSkeletonElement(const char* szCurrent, const char** szCur // ------------------------------------------------------------------------------------------------ // Parse a triangle -void SMDImporter::ParseTriangle(const char* szCurrent, const char** szCurrentOut) { +void SMDImporter::ParseTriangle(const char *szCurrent, const char **szCurrentOut, const char *end) { asTriangles.emplace_back(); SMD::Face& face = asTriangles.back(); - if(!SkipSpaces(szCurrent,&szCurrent)) { + if(!SkipSpaces(szCurrent, &szCurrent, end)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing a triangle"); return; } @@ -961,19 +966,19 @@ void SMDImporter::ParseTriangle(const char* szCurrent, const char** szCurrentOut // ... and get the index that belongs to this file name face.iTexture = GetTextureIndex(std::string(szLast,(uintptr_t)szCurrent-(uintptr_t)szLast)); - SkipSpacesAndLineEnd(szCurrent,&szCurrent); + SkipSpacesAndLineEnd(szCurrent, &szCurrent, end); // load three vertices for (auto &avVertex : face.avVertices) { - ParseVertex(szCurrent,&szCurrent, avVertex); + ParseVertex(szCurrent, &szCurrent, end, avVertex); } *szCurrentOut = szCurrent; } // ------------------------------------------------------------------------------------------------ // Parse a float -bool SMDImporter::ParseFloat(const char* szCurrent, const char** szCurrentOut, float& out) { - if(!SkipSpaces(&szCurrent)) { +bool SMDImporter::ParseFloat(const char *szCurrent, const char **szCurrentOut, const char *end, float &out) { + if (!SkipSpaces(&szCurrent, end)) { return false; } @@ -983,8 +988,8 @@ bool SMDImporter::ParseFloat(const char* szCurrent, const char** szCurrentOut, f // ------------------------------------------------------------------------------------------------ // Parse an unsigned int -bool SMDImporter::ParseUnsignedInt(const char* szCurrent, const char** szCurrentOut, unsigned int& out) { - if(!SkipSpaces(&szCurrent)) { +bool SMDImporter::ParseUnsignedInt(const char *szCurrent, const char **szCurrentOut, const char *end, unsigned int &out) { + if(!SkipSpaces(&szCurrent, end)) { return false; } @@ -994,8 +999,8 @@ bool SMDImporter::ParseUnsignedInt(const char* szCurrent, const char** szCurrent // ------------------------------------------------------------------------------------------------ // Parse a signed int -bool SMDImporter::ParseSignedInt(const char* szCurrent, const char** szCurrentOut, int& out) { - if(!SkipSpaces(&szCurrent)) { +bool SMDImporter::ParseSignedInt(const char *szCurrent, const char **szCurrentOut, const char *end, int &out) { + if(!SkipSpaces(&szCurrent, end)) { return false; } @@ -1006,37 +1011,37 @@ bool SMDImporter::ParseSignedInt(const char* szCurrent, const char** szCurrentOu // ------------------------------------------------------------------------------------------------ // Parse a vertex void SMDImporter::ParseVertex(const char* szCurrent, - const char** szCurrentOut, SMD::Vertex& vertex, + const char **szCurrentOut, const char *end, SMD::Vertex &vertex, bool bVASection /*= false*/) { - if (SkipSpaces(&szCurrent) && IsLineEnd(*szCurrent)) { - SkipSpacesAndLineEnd(szCurrent,&szCurrent); - return ParseVertex(szCurrent,szCurrentOut,vertex,bVASection); + if (SkipSpaces(&szCurrent, end) && IsLineEnd(*szCurrent)) { + SkipSpacesAndLineEnd(szCurrent,&szCurrent, end); + return ParseVertex(szCurrent, szCurrentOut, end, vertex, bVASection); } - if(!ParseSignedInt(szCurrent,&szCurrent,(int&)vertex.iParentNode)) { + if(!ParseSignedInt(szCurrent, &szCurrent, end, (int&)vertex.iParentNode)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.parent"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.x)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.pos.x)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.x"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.y)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.pos.y)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.y"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.z)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.pos.z)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.z"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.x)) { + if(!ParseFloat(szCurrent,&szCurrent,end, (float&)vertex.nor.x)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.x"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.y)) { + if(!ParseFloat(szCurrent,&szCurrent, end, (float&)vertex.nor.y)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.y"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.z)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.nor.z)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.z"); SMDI_PARSE_RETURN; } @@ -1045,11 +1050,11 @@ void SMDImporter::ParseVertex(const char* szCurrent, SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.x)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.uv.x)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.x"); SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.y)) { + if(!ParseFloat(szCurrent, &szCurrent, end, (float&)vertex.uv.y)) { LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.y"); SMDI_PARSE_RETURN; } @@ -1057,16 +1062,16 @@ void SMDImporter::ParseVertex(const char* szCurrent, // now read the number of bones affecting this vertex // all elements from now are fully optional, we don't need them unsigned int iSize = 0; - if(!ParseUnsignedInt(szCurrent,&szCurrent,iSize)) { + if(!ParseUnsignedInt(szCurrent, &szCurrent, end, iSize)) { SMDI_PARSE_RETURN; } vertex.aiBoneLinks.resize(iSize,std::pair(0,0.0f)); for (auto &aiBoneLink : vertex.aiBoneLinks) { - if(!ParseUnsignedInt(szCurrent,&szCurrent,aiBoneLink.first)) { + if(!ParseUnsignedInt(szCurrent, &szCurrent, end, aiBoneLink.first)) { SMDI_PARSE_RETURN; } - if(!ParseFloat(szCurrent,&szCurrent,aiBoneLink.second)) { + if(!ParseFloat(szCurrent, &szCurrent, end, aiBoneLink.second)) { SMDI_PARSE_RETURN; } } @@ -1075,4 +1080,6 @@ void SMDImporter::ParseVertex(const char* szCurrent, SMDI_PARSE_RETURN; } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_SMD_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.h b/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.h index db882a241..c2f2f0a49 100644 --- a/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.h +++ b/Engine/lib/assimp/code/AssetLib/SMD/SMDLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -87,10 +87,10 @@ struct Vertex { */ struct Face { Face() AI_NO_EXCEPT : - iTexture(0x0), avVertices{} { + iTexture(0x0) { // empty } - + //! Texture index for the face unsigned int iTexture; @@ -162,7 +162,7 @@ struct Bone { class ASSIMP_API SMDImporter : public BaseImporter { public: SMDImporter(); - ~SMDImporter() override; + ~SMDImporter() override = default; // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. @@ -206,7 +206,7 @@ protected: * the next section (or to EOF) */ void ParseTrianglesSection(const char* szCurrent, - const char** szCurrentOut); + const char **szCurrentOut, const char *end); // ------------------------------------------------------------------- /** Parse the vertex animation section in VTA files @@ -216,7 +216,7 @@ protected: * the next section (or to EOF) */ void ParseVASection(const char* szCurrent, - const char** szCurrentOut); + const char **szCurrentOu, const char *end); // ------------------------------------------------------------------- /** Parse the nodes section of the SMD file @@ -226,7 +226,7 @@ protected: * the next section (or to EOF) */ void ParseNodesSection(const char* szCurrent, - const char** szCurrentOut); + const char **szCurrentOut, const char *end); // ------------------------------------------------------------------- /** Parse the skeleton section of the SMD file @@ -236,7 +236,7 @@ protected: * the next section (or to EOF) */ void ParseSkeletonSection(const char* szCurrent, - const char** szCurrentOut); + const char **szCurrentOut, const char *end); // ------------------------------------------------------------------- /** Parse a single triangle in the SMD file @@ -245,8 +245,7 @@ protected: * \param szCurrentOut Receives the output cursor position */ void ParseTriangle(const char* szCurrent, - const char** szCurrentOut); - + const char **szCurrentOut, const char *end); // ------------------------------------------------------------------- /** Parse a single vertex in the SMD file @@ -256,7 +255,7 @@ protected: * \param vertex Vertex to be filled */ void ParseVertex(const char* szCurrent, - const char** szCurrentOut, SMD::Vertex& vertex, + const char **szCurrentOut, const char *end, SMD::Vertex &vertex, bool bVASection = false); // ------------------------------------------------------------------- @@ -271,32 +270,31 @@ protected: /** Parse a line in the skeleton section */ void ParseSkeletonElement(const char* szCurrent, - const char** szCurrentOut,int iTime); + const char **szCurrentOut, const char *end, int iTime); // ------------------------------------------------------------------- /** Parse a line in the nodes section */ void ParseNodeInfo(const char* szCurrent, - const char** szCurrentOut); - + const char **szCurrentOut, const char *end); // ------------------------------------------------------------------- /** Parse a floating-point value */ bool ParseFloat(const char* szCurrent, - const char** szCurrentOut, float& out); + const char **szCurrentOut, const char *end, float &out); // ------------------------------------------------------------------- /** Parse an unsigned integer. There may be no sign! */ bool ParseUnsignedInt(const char* szCurrent, - const char** szCurrentOut, unsigned int& out); + const char **szCurrentOut, const char *end, unsigned int &out); // ------------------------------------------------------------------- /** Parse a signed integer. Signs (+,-) are handled. */ bool ParseSignedInt(const char* szCurrent, - const char** szCurrentOut, int& out); + const char **szCurrentOut, const char *end, int &out); // ------------------------------------------------------------------- /** Fix invalid time values in the file @@ -304,7 +302,7 @@ protected: void FixTimeValues(); // ------------------------------------------------------------------- - /** Add all children of a bone as subnodes to a node + /** Add all children of a bone as sub-nodes to a node * \param pcNode Parent node * \param iParent Parent bone index */ @@ -329,17 +327,15 @@ protected: // ------------------------------------------------------------------- - inline bool SkipLine( const char* in, const char** out) - { - Assimp::SkipLine(in,out); + inline bool SkipLine( const char* in, const char** out, const char *end) { + Assimp::SkipLine(in, out, end); ++iLineNumber; return true; } // ------------------------------------------------------------------- - inline bool SkipSpacesAndLineEnd( const char* in, const char** out) - { + inline bool SkipSpacesAndLineEnd(const char *in, const char **out, const char *end) { ++iLineNumber; - return Assimp::SkipSpacesAndLineEnd(in,out); + return Assimp::SkipSpacesAndLineEnd(in, out, end); } private: @@ -349,6 +345,7 @@ private: /** Buffer to hold the loaded file */ std::vector mBuffer; + char *mEnd; /** Output scene to be filled */ diff --git a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp index d4456e674..d7f512cbb 100644 --- a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp +++ b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -45,11 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "STEPFileEncoding.h" #include -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include -#endif +#include "utf8.h" #include diff --git a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h index 950c9b3e2..6988a5822 100644 --- a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h +++ b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileEncoding.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp index 2bcfa1755..6bc8981f1 100644 --- a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp +++ b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,9 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file STEPFileReader.cpp +/** + * @file STEPFileReader.cpp * @brief Implementation of the STEP file parser, which fills a - * STEP::DB with data read from a file. + * STEP::DB with data read from a file. */ #include "STEPFileReader.h" @@ -58,34 +58,28 @@ using namespace Assimp; namespace EXPRESS = STEP::EXPRESS; // ------------------------------------------------------------------------------------------------ -std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = std::string()) -{ +std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = std::string()) { return line == STEP::SyntaxError::LINE_NOT_SPECIFIED ? prefix+s : static_cast( (Formatter::format(),prefix,"(line ",line,") ",s) ); } // ------------------------------------------------------------------------------------------------ -std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = std::string()) -{ +std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = std::string()) { return entity == STEP::TypeError::ENTITY_NOT_SPECIFIED ? prefix+s : static_cast( (Formatter::format(),prefix,"(entity #",entity,") ",s)); } // ------------------------------------------------------------------------------------------------ -STEP::SyntaxError::SyntaxError (const std::string& s,uint64_t line /* = LINE_NOT_SPECIFIED */) -: DeadlyImportError(AddLineNumber(s,line)) -{ - +STEP::SyntaxError::SyntaxError (const std::string& s,uint64_t line) : DeadlyImportError(AddLineNumber(s,line)) { + // empty } // ------------------------------------------------------------------------------------------------ -STEP::TypeError::TypeError (const std::string& s,uint64_t entity /* = ENTITY_NOT_SPECIFIED */,uint64_t line /*= LINE_NOT_SPECIFIED*/) -: DeadlyImportError(AddLineNumber(AddEntityID(s,entity),line)) -{ - +STEP::TypeError::TypeError (const std::string& s,uint64_t entity, uint64_t line) : DeadlyImportError(AddLineNumber(AddEntityID(s,entity),line)) { + // empty } -static const char *ISO_Token = "ISO-10303-21;"; -static const char *FILE_SCHEMA_Token = "FILE_SCHEMA"; +static constexpr char ISO_Token[] = "ISO-10303-21;"; +static constexpr char FILE_SCHEMA_Token[] = "FILE_SCHEMA"; // ------------------------------------------------------------------------------------------------ STEP::DB* STEP::ReadFileHeader(std::shared_ptr stream) { std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(std::move(stream))); @@ -110,8 +104,9 @@ STEP::DB* STEP::ReadFileHeader(std::shared_ptr stream) { if (s.substr(0,11) == FILE_SCHEMA_Token) { const char* sz = s.c_str()+11; - SkipSpaces(sz,&sz); - std::shared_ptr< const EXPRESS::DataType > schema = EXPRESS::DataType::Parse(sz); + const char *end = s.c_str() + s.size(); + SkipSpaces(sz,&sz, end); + std::shared_ptr< const EXPRESS::DataType > schema = EXPRESS::DataType::Parse(sz, end); // the file schema should be a regular list entity, although it usually contains exactly one entry // since the list itself is contained in a regular parameter list, we actually have @@ -304,10 +299,10 @@ void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, } // ------------------------------------------------------------------------------------------------ -std::shared_ptr EXPRESS::DataType::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= nullptr*/) +std::shared_ptr EXPRESS::DataType::Parse(const char*& inout, const char *end, uint64_t line, const EXPRESS::ConversionSchema* schema /*= nullptr*/) { const char* cur = inout; - SkipSpaces(&cur); + SkipSpaces(&cur, end); if (*cur == ',' || IsSpaceOrNewLine(*cur)) { throw STEP::SyntaxError("unexpected token, expected parameter",line); } @@ -325,7 +320,7 @@ std::shared_ptr EXPRESS::DataType::Parse(const char*& i std::transform(s.begin(),s.end(),s.begin(),&ai_tolower ); if (schema->IsKnownToken(s)) { for(cur = t+1;*cur++ != '(';); - std::shared_ptr dt = Parse(cur); + std::shared_ptr dt = Parse(cur, end); inout = *cur ? cur+1 : cur; return dt; } @@ -348,7 +343,7 @@ std::shared_ptr EXPRESS::DataType::Parse(const char*& i else if (*cur == '(' ) { // start of an aggregate, further parsing is done by the LIST factory constructor inout = cur; - return EXPRESS::LIST::Parse(inout,line,schema); + return EXPRESS::LIST::Parse(inout, end, line, schema); } else if (*cur == '.' ) { // enum (includes boolean) @@ -427,9 +422,10 @@ std::shared_ptr EXPRESS::DataType::Parse(const char*& i } // ------------------------------------------------------------------------------------------------ -std::shared_ptr EXPRESS::LIST::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= nullptr*/) { +std::shared_ptr EXPRESS::LIST::Parse(const char*& inout, const char *end, + uint64_t line, const EXPRESS::ConversionSchema* schema) { const std::shared_ptr list = std::make_shared(); - EXPRESS::LIST::MemberList& members = list->members; + EXPRESS::LIST::MemberList& cur_members = list->members; const char* cur = inout; if (*cur++ != '(') { @@ -442,19 +438,19 @@ std::shared_ptr EXPRESS::LIST::Parse(const char*& inout,uin count += (*c == ',' ? 1 : 0); } - members.reserve(count); + cur_members.reserve(count); for(;;++cur) { if (!*cur) { throw STEP::SyntaxError("unexpected end of line while reading list"); } - SkipSpaces(cur,&cur); + SkipSpaces(cur,&cur, end); if (*cur == ')') { break; } - members.push_back( EXPRESS::DataType::Parse(cur,line,schema)); - SkipSpaces(cur,&cur); + cur_members.push_back(EXPRESS::DataType::Parse(cur, end, line, schema)); + SkipSpaces(cur, &cur, end); if (*cur != ',') { if (*cur == ')') { @@ -464,7 +460,7 @@ std::shared_ptr EXPRESS::LIST::Parse(const char*& inout,uin } } - inout = cur+1; + inout = cur + 1; return list; } @@ -543,7 +539,8 @@ void STEP::LazyObject::LazyInit() const { } const char* acopy = args; - std::shared_ptr conv_args = EXPRESS::LIST::Parse(acopy,(uint64_t)STEP::SyntaxError::LINE_NOT_SPECIFIED,&db.GetSchema()); + const char *end = acopy + std::strlen(args); + std::shared_ptr conv_args = EXPRESS::LIST::Parse(acopy, end, (uint64_t)STEP::SyntaxError::LINE_NOT_SPECIFIED,&db.GetSchema()); delete[] args; args = nullptr; diff --git a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.h b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.h index 8a57937c0..85a7c5cb0 100644 --- a/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.h +++ b/Engine/lib/assimp/code/AssetLib/STEPParser/STEPFileReader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,8 +60,7 @@ void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const* /// @brief Helper to read a file. template -inline -void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N], const char* const (&arr2)[N2]) { +inline void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N], const char* const (&arr2)[N2]) { return ReadFile(db,scheme,arr,N,arr2,N2); } diff --git a/Engine/lib/assimp/code/AssetLib/STL/STLExporter.cpp b/Engine/lib/assimp/code/AssetLib/STL/STLExporter.cpp index 9bbc2063f..0ff96296a 100644 --- a/Engine/lib/assimp/code/AssetLib/STL/STLExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/STL/STLExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/STL/STLExporter.h b/Engine/lib/assimp/code/AssetLib/STL/STLExporter.h index 066dcfefd..b751e196c 100644 --- a/Engine/lib/assimp/code/AssetLib/STL/STLExporter.h +++ b/Engine/lib/assimp/code/AssetLib/STL/STLExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/STL/STLLoader.cpp b/Engine/lib/assimp/code/AssetLib/STL/STLLoader.cpp index 2379ad619..90c504d0d 100644 --- a/Engine/lib/assimp/code/AssetLib/STL/STLLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/STL/STLLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,11 +52,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { namespace { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Stereolithography (STL) Importer", "", "", @@ -98,7 +98,7 @@ static bool IsAsciiSTL(const char *buffer, size_t fileSize) { const char *bufferEnd = buffer + fileSize; - if (!SkipSpaces(&buffer)) { + if (!SkipSpaces(&buffer, bufferEnd)) { return false; } @@ -129,7 +129,7 @@ STLImporter::STLImporter() : mBuffer(), mFileSize(0), mScene() { - // empty + // empty } // ------------------------------------------------------------------------------------------------ @@ -166,7 +166,7 @@ void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::unique_ptr file(pIOHandler->Open(pFile, "rb")); // Check whether we can read from the file - if (file.get() == nullptr) { + if (file == nullptr) { throw DeadlyImportError("Failed to open STL file ", pFile, "."); } @@ -181,7 +181,7 @@ void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy mBuffer = &buffer2[0]; // the default vertex color is light gray. - mClrColorDefault.r = mClrColorDefault.g = mClrColorDefault.b = mClrColorDefault.a = (ai_real)0.6; + mClrColorDefault.r = mClrColorDefault.g = mClrColorDefault.b = mClrColorDefault.a = 0.6f; // allocate a single node mScene->mRootNode = new aiNode(); @@ -209,7 +209,7 @@ void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_SPECULAR); - clrDiffuse = aiColor4D(ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0)); + clrDiffuse = aiColor4D(0.05f, 0.05f, 0.05f, 1.0f); pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_AMBIENT); mScene->mNumMaterials = 1; @@ -244,20 +244,20 @@ void STLImporter::LoadASCIIFile(aiNode *root) { aiNode *node = new aiNode; node->mParent = root; nodes.push_back(node); - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); ai_assert(!IsLineEnd(sz)); sz += 5; // skip the "solid" - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); const char *szMe = sz; - while (!::IsSpaceOrNewLine(*sz)) { + while (!IsSpaceOrNewLine(*sz)) { sz++; } size_t temp = (size_t)(sz - szMe); // setup the name of the node - if ( temp ) { - if (temp >= MAXLEN) { + if (temp) { + if (temp >= AI_MAXLEN) { throw DeadlyImportError("STL: Node name too long"); } std::string name(szMe, temp); @@ -270,7 +270,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { unsigned int faceVertexCounter = 3; for (;;) { // go to the next token - if (!SkipSpacesAndLineEnd(&sz)) { + if (!SkipSpacesAndLineEnd(&sz, bufferEnd)) { // seems we're finished although there was no end marker ASSIMP_LOG_WARN("STL: unexpected EOF. \'endsolid\' keyword was expected"); break; @@ -284,7 +284,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { faceVertexCounter = 0; sz += 6; - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); if (strncmp(sz, "normal", 6)) { ASSIMP_LOG_WARN("STL: a facet normal vector was expected but not found"); } else { @@ -293,17 +293,17 @@ void STLImporter::LoadASCIIFile(aiNode *root) { } aiVector3D vn; sz += 7; - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); sz = fast_atoreal_move(sz, (ai_real &)vn.x); - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); sz = fast_atoreal_move(sz, (ai_real &)vn.y); - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); sz = fast_atoreal_move(sz, (ai_real &)vn.z); normalBuffer.emplace_back(vn); normalBuffer.emplace_back(vn); normalBuffer.emplace_back(vn); } - } else if (!strncmp(sz, "vertex", 6) && ::IsSpaceOrNewLine(*(sz + 6))) { // vertex 1.50000 1.50000 0.00000 + } else if (!strncmp(sz, "vertex", 6) && IsSpaceOrNewLine(*(sz + 6))) { // vertex 1.50000 1.50000 0.00000 if (faceVertexCounter >= 3) { ASSIMP_LOG_ERROR("STL: a facet with more than 3 vertices has been found"); ++sz; @@ -312,27 +312,27 @@ void STLImporter::LoadASCIIFile(aiNode *root) { throw DeadlyImportError("STL: unexpected EOF while parsing facet"); } sz += 7; - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); positionBuffer.emplace_back(); aiVector3D *vn = &positionBuffer.back(); sz = fast_atoreal_move(sz, (ai_real &)vn->x); - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); sz = fast_atoreal_move(sz, (ai_real &)vn->y); - SkipSpaces(&sz); + SkipSpaces(&sz, bufferEnd); sz = fast_atoreal_move(sz, (ai_real &)vn->z); faceVertexCounter++; } } else if (!::strncmp(sz, "endsolid", 8)) { do { ++sz; - } while (!::IsLineEnd(*sz)); - SkipSpacesAndLineEnd(&sz); + } while (!IsLineEnd(*sz)); + SkipSpacesAndLineEnd(&sz, bufferEnd); // finished! break; } else { // else skip the whole identifier do { ++sz; - } while (!::IsSpaceOrNewLine(*sz)); + } while (!IsSpaceOrNewLine(*sz)); } } @@ -349,14 +349,14 @@ void STLImporter::LoadASCIIFile(aiNode *root) { throw DeadlyImportError("Normal buffer size does not match position buffer size"); } - // only process positionbuffer when filled, else exception when accessing with index operator + // only process position buffer when filled, else exception when accessing with index operator // see line 353: only warning is triggered - // see line 373(now): access to empty positionbuffer with index operator forced exception + // see line 373(now): access to empty position buffer with index operator forced exception if (!positionBuffer.empty()) { pMesh->mNumFaces = static_cast(positionBuffer.size() / 3); pMesh->mNumVertices = static_cast(positionBuffer.size()); pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; - for (size_t i=0; imNumVertices; ++i ) { + for (size_t i = 0; i < pMesh->mNumVertices; ++i) { pMesh->mVertices[i].x = positionBuffer[i].x; pMesh->mVertices[i].y = positionBuffer[i].y; pMesh->mVertices[i].z = positionBuffer[i].z; @@ -366,7 +366,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { // also only process normalBuffer when filled, else exception when accessing with index operator if (!normalBuffer.empty()) { pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; - for (size_t i=0; imNumVertices; ++i ) { + for (size_t i = 0; i < pMesh->mNumVertices; ++i) { pMesh->mNormals[i].x = normalBuffer[i].x; pMesh->mNormals[i].y = normalBuffer[i].y; pMesh->mNormals[i].z = normalBuffer[i].z; @@ -450,9 +450,8 @@ bool STLImporter::LoadBinaryFile() { aiVector3D *vp = pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; aiVector3D *vn = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; - typedef aiVector3t aiVector3F; - aiVector3F *theVec; - aiVector3F theVec3F; + aiVector3f *theVec; + aiVector3f theVec3F; for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { // NOTE: Blender sometimes writes empty normals ... this is not @@ -460,8 +459,8 @@ bool STLImporter::LoadBinaryFile() { // There's one normal for the face in the STL; use it three times // for vertex normals - theVec = (aiVector3F *)sz; - ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + theVec = (aiVector3f *)sz; + ::memcpy(&theVec3F, theVec, sizeof(aiVector3f)); vn->x = theVec3F.x; vn->y = theVec3F.y; vn->z = theVec3F.z; @@ -471,7 +470,7 @@ bool STLImporter::LoadBinaryFile() { vn += 3; // vertex 1 - ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + ::memcpy(&theVec3F, theVec, sizeof(aiVector3f)); vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z; @@ -479,7 +478,7 @@ bool STLImporter::LoadBinaryFile() { ++vp; // vertex 2 - ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + ::memcpy(&theVec3F, theVec, sizeof(aiVector3f)); vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z; @@ -487,7 +486,7 @@ bool STLImporter::LoadBinaryFile() { ++vp; // vertex 3 - ::memcpy(&theVec3F, theVec, sizeof(aiVector3F)); + ::memcpy(&theVec3F, theVec, sizeof(aiVector3f)); vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z; @@ -570,4 +569,6 @@ void STLImporter::pushMeshesToNode(std::vector &meshIndices, aiNod meshIndices.clear(); } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_STL_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/STL/STLLoader.h b/Engine/lib/assimp/code/AssetLib/STL/STLLoader.h index 0be6a95f0..cc6ab9607 100644 --- a/Engine/lib/assimp/code/AssetLib/STL/STLLoader.h +++ b/Engine/lib/assimp/code/AssetLib/STL/STLLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Step/STEPFile.h b/Engine/lib/assimp/code/AssetLib/Step/STEPFile.h index ce7b357ad..1fd24b329 100644 --- a/Engine/lib/assimp/code/AssetLib/Step/STEPFile.h +++ b/Engine/lib/assimp/code/AssetLib/Step/STEPFile.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -82,8 +82,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // this is intended as stress test - by default, entities are evaluated // lazily and therefore not unless needed. -//#define ASSIMP_IFC_TEST - namespace Assimp { // ******************************************************************************** @@ -121,7 +119,7 @@ namespace STEP { // ------------------------------------------------------------------------------- /** Exception class used by the STEP loading & parsing code. It is typically - * coupled with a line number. + * coupled with a line number. */ // ------------------------------------------------------------------------------- struct SyntaxError : DeadlyImportError { @@ -195,42 +193,34 @@ public: template const T *ResolveSelectPtr(const DB &db) const { const EXPRESS::ENTITY *e = ToPtr(); - return e ? Couple(db).MustGetObject(*e)->template ToPtr() : (const T *)0; + return e ? Couple(db).MustGetObject(*e)->template ToPtr() : (const T *)nullptr; } public: - /** parse a variable from a string and set 'inout' to the character - * behind the last consumed character. An optional schema enables, - * if specified, automatic conversion of custom data types. - * - * @throw SyntaxError - */ - static std::shared_ptr Parse(const char *&inout, - uint64_t line = SyntaxError::LINE_NOT_SPECIFIED, - const EXPRESS::ConversionSchema *schema = NULL); + /// @brief Parse a variable from a string and set 'inout' to the character behind the last consumed character. + /// + /// An optional schema enables, if specified, automatic conversion of custom data types. + /// + /// @throw SyntaxError + static std::shared_ptr Parse(const char *&inout, const char *end, + uint64_t line = SyntaxError::LINE_NOT_SPECIFIED, const EXPRESS::ConversionSchema *schema = nullptr); }; typedef DataType SELECT; typedef DataType LOGICAL; // ------------------------------------------------------------------------------- -/** Sentinel class to represent explicitly unset (optional) fields ($) */ +/// Sentinel class to represent explicitly unset (optional) fields ($) // ------------------------------------------------------------------------------- -class UNSET : public DataType { -public: -private: -}; +class UNSET : public DataType {}; // ------------------------------------------------------------------------------- -/** Sentinel class to represent explicitly derived fields (*) */ +/// Sentinel class to represent explicitly derived fields (*) // ------------------------------------------------------------------------------- -class ISDERIVED : public DataType { -public: -private: -}; +class ISDERIVED : public DataType {}; // ------------------------------------------------------------------------------- -/** Shared implementation for some of the primitive data type, i.e. int, float +/** Shared implementation for some of the primitive data type, i.e. int, float */ // ------------------------------------------------------------------------------- template @@ -278,7 +268,7 @@ public: typedef ENUMERATION BOOLEAN; // ------------------------------------------------------------------------------- -/** This is just a reference to an entity/object somewhere else +/** This is just a reference to an entity/object somewhere else */ // ------------------------------------------------------------------------------- class ENTITY : public PrimitiveDataType { @@ -302,11 +292,11 @@ public: } public: - /** @see DaraType::Parse + /** @see DaraType::Parse */ - static std::shared_ptr Parse(const char *&inout, + static std::shared_ptr Parse(const char *&inout, const char *end, uint64_t line = SyntaxError::LINE_NOT_SPECIFIED, - const EXPRESS::ConversionSchema *schema = NULL); + const EXPRESS::ConversionSchema *schema = nullptr); private: typedef std::vector> MemberList; @@ -322,7 +312,7 @@ public: // ------------------------------------------------------------------------------- /* Not exactly a full EXPRESS schema but rather a list of conversion functions * to extract valid C++ objects out of a STEP file. Those conversion functions - * may, however, perform further schema validations. + * may, however, perform further schema validations. */ // ------------------------------------------------------------------------------- class ConversionSchema { @@ -384,7 +374,7 @@ struct HeaderInfo { }; // ------------------------------------------------------------------------------ -/** Base class for all concrete object instances +/** Base class for all concrete object instances */ // ------------------------------------------------------------------------------ class Object { @@ -511,7 +501,7 @@ private: // ------------------------------------------------------------------------------ /** A LazyObject is created when needed. Before this happens, we just keep - * the text line that contains the object definition. + * the text line that contains the object definition. */ // ------------------------------------------------------------------------------- class LazyObject { @@ -539,6 +529,7 @@ public: template const T &To() const { + return dynamic_cast(**this); } @@ -589,12 +580,12 @@ private: }; template -inline bool operator==(const std::shared_ptr &lo, T whatever) { +inline bool operator == (const std::shared_ptr &lo, T whatever) { return *lo == whatever; // XXX use std::forward if we have 0x } template -inline bool operator==(const std::pair> &lo, T whatever) { +inline bool operator == (const std::pair> &lo, T whatever) { return *(lo.second) == whatever; // XXX use std::forward if we have 0x } @@ -607,18 +598,30 @@ struct Lazy { Lazy(const LazyObject *obj = nullptr) : obj(obj) {} operator const T *() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->ToPtr(); } operator const T &() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->To(); } const T &operator*() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->To(); } const T *operator->() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return &obj->To(); } diff --git a/Engine/lib/assimp/code/AssetLib/Step/StepExporter.cpp b/Engine/lib/assimp/code/AssetLib/Step/StepExporter.cpp index e13c9edab..f5ccf88f6 100644 --- a/Engine/lib/assimp/code/AssetLib/Step/StepExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/Step/StepExporter.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -37,11 +36,9 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -@author: Richard Steffen, 2015 ---------------------------------------------------------------------- */ - #ifndef ASSIMP_BUILD_NO_EXPORT #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Step/StepExporter.h b/Engine/lib/assimp/code/AssetLib/Step/StepExporter.h index a02262659..9ae5fb820 100644 --- a/Engine/lib/assimp/code/AssetLib/Step/StepExporter.h +++ b/Engine/lib/assimp/code/AssetLib/Step/StepExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.cpp b/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.cpp index dcf01461a..150ebd011 100644 --- a/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,9 +51,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Terragen Heightmap Importer", "", "", @@ -73,10 +73,6 @@ TerragenImporter::TerragenImporter() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -TerragenImporter::~TerragenImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { @@ -230,8 +226,8 @@ void TerragenImporter::InternReadFile(const std::string &pFile, } // Get to the next chunk (4 byte aligned) - unsigned dtt = reader.GetCurrentPos(); - if (dtt & 0x3) { + unsigned dtt = reader.GetCurrentPos() & 0x3; + if (dtt) { reader.IncPtr(4 - dtt); } } @@ -244,4 +240,6 @@ void TerragenImporter::InternReadFile(const std::string &pFile, pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN; } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_TERRAGEN_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.h b/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.h index cb9ff9166..2d529464b 100644 --- a/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Terragen/TerragenLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -73,7 +73,7 @@ namespace Assimp { class TerragenImporter : public BaseImporter { public: TerragenImporter(); - ~TerragenImporter() override; + ~TerragenImporter() override = default; // ------------------------------------------------------------------- bool CanRead(const std::string &pFile, IOSystem *pIOHandler, diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoader.cpp b/Engine/lib/assimp/code/AssetLib/USD/USDLoader.cpp new file mode 100644 index 000000000..752332abf --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoader.cpp @@ -0,0 +1,124 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------- +*/ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoader.h" +#include "USDLoaderUtil.h" +#include "USDPreprocessor.h" + +static constexpr aiImporterDesc desc = { + "USD Object Importer", + "", + "", + "https://en.wikipedia.org/wiki/Universal_Scene_Description/", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "usd usda usdc usdz" +}; + +namespace Assimp { +using namespace std; + +// Constructor to be privately used by Importer +USDImporter::USDImporter() : + impl(USDImporterImplTinyusdz()) { +} + +// ------------------------------------------------------------------------------------------------ + +bool USDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool) const { + // Based on token + static const uint32_t usdcTokens[] = { AI_MAKE_MAGIC("PXR-USDC") }; + bool canRead = CheckMagicToken(pIOHandler, pFile, usdcTokens, AI_COUNT_OF(usdcTokens)); + if (canRead) { + return canRead; + } + + // Based on extension + // TODO: confirm OK to replace this w/SimpleExtensionCheck() below + canRead = isUsd(pFile) || isUsda(pFile) || isUsdc(pFile) || isUsdz(pFile); + if (canRead) { + return canRead; + } + canRead = SimpleExtensionCheck(pFile, "usd", "usda", "usdc", "usdz"); + return canRead; +} + +const aiImporterDesc *USDImporter::GetInfo() const { + return &desc; +} + +void USDImporter::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + impl.InternReadFile( + pFile, + pScene, + pIOHandler); +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER + diff --git a/Engine/lib/assimp/samples/SharedCode/UTFConverter.h b/Engine/lib/assimp/code/AssetLib/USD/USDLoader.h similarity index 54% rename from Engine/lib/assimp/samples/SharedCode/UTFConverter.h rename to Engine/lib/assimp/code/AssetLib/USD/USDLoader.h index 17e89ee4d..8400dc42c 100644 --- a/Engine/lib/assimp/samples/SharedCode/UTFConverter.h +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoader.h @@ -1,17 +1,14 @@ /* ---------------------------------------------------------------------------- Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - +---------------------------------------------------------------------- +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: +with or without modification, are permitted provided that the +following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the @@ -38,55 +35,44 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- +---------------------------------------------------------------------- */ -#ifndef ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H -#define ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_H_INCLUDED +#define AI_USDLOADER_H_INCLUDED -#include -#include -#include +#include +#include +#include +#include -namespace AssimpSamples { -namespace SharedCode { +#include "USDLoaderImplTinyusdz.h" -// Used to convert between multibyte and unicode strings. -class UTFConverter { - using UTFConverterImpl = std::wstring_convert, wchar_t>; +namespace Assimp { +class USDImporter : public BaseImporter { public: - UTFConverter(const char* s) : - s_(s), - ws_(impl_.from_bytes(s)) { - } - UTFConverter(const wchar_t* s) : - s_(impl_.to_bytes(s)), - ws_(s) { - } - UTFConverter(const std::string& s) : - s_(s), - ws_(impl_.from_bytes(s)) { - } - UTFConverter(const std::wstring& s) : - s_(impl_.to_bytes(s)), - ws_(s) { - } - inline const char* c_str() const { - return s_.c_str(); - } - inline const std::string& str() const { - return s_; - } - inline const wchar_t* c_wstr() const { - return ws_.c_str(); - } + USDImporter(); + ~USDImporter() override = default; + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) override; private: - static UTFConverterImpl impl_; - std::string s_; - std::wstring ws_; + USDImporterImplTinyusdz impl; }; -} -} - -#endif // ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H +} // namespace Assimp +#endif // AI_USDLOADER_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp new file mode 100644 index 000000000..a817afdbd --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp @@ -0,0 +1,1001 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "assimp/MemoryIOWrapper.h" +#include +#include + +#include "io-util.hh" // namespace tinyusdz::io +#include "tydra/scene-access.hh" +#include "tydra/shader-network.hh" +#include "USDLoaderImplTinyusdzHelper.h" +#include "USDLoaderImplTinyusdz.h" +#include "USDLoaderUtil.h" +#include "USDPreprocessor.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { + static constexpr char TAG[] = "tinyusdz loader"; +} + +namespace Assimp { +using namespace std; + +void USDImporterImplTinyusdz::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + // Grab filename for logging purposes + size_t pos = pFile.find_last_of('/'); + string basePath = pFile.substr(0, pos); + string nameWExt = pFile.substr(pos + 1); + stringstream ss; + ss.str(""); + ss << "InternReadFile(): model" << nameWExt; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + bool is_load_from_mem{ pFile.substr(0, AI_MEMORYIO_MAGIC_FILENAME_LENGTH) == AI_MEMORYIO_MAGIC_FILENAME }; + std::vector in_mem_data; + if (is_load_from_mem) { + auto stream_closer = [pIOHandler](IOStream *pStream) { + pIOHandler->Close(pStream); + }; + std::unique_ptr file_stream(pIOHandler->Open(pFile, "rb"), stream_closer); + if (!file_stream) { + throw DeadlyImportError("Failed to open file ", pFile, "."); + } + size_t file_size{ file_stream->FileSize() }; + in_mem_data.resize(file_size); + file_stream->Read(in_mem_data.data(), 1, file_size); + } + + bool ret{ false }; + tinyusdz::USDLoadOptions options; + tinyusdz::Stage stage; + std::string warn, err; + bool is_usdz{ false }; + if (isUsdc(pFile)) { + ret = is_load_from_mem ? LoadUSDCFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) : + LoadUSDCFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsda(pFile)) { + ret = is_load_from_mem ? LoadUSDAFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) : + LoadUSDAFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsdz(pFile)) { + ret = is_load_from_mem ? LoadUSDZFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) : + LoadUSDZFromFile(pFile, &stage, &warn, &err, options); + is_usdz = true; + ss.str(""); + ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsd(pFile)) { + ret = is_load_from_mem ? LoadUSDFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) : + LoadUSDFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + if (warn.empty() && err.empty()) { + ss.str(""); + ss << "InternReadFile(): load free of warnings/errors"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + } + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + tinyusdz::tydra::RenderScene render_scene; + tinyusdz::tydra::RenderSceneConverter converter; + tinyusdz::tydra::RenderSceneConverterEnv env(stage); + std::string usd_basedir = tinyusdz::io::GetBaseDir(pFile); + env.set_search_paths({ usd_basedir }); // {} needed to convert to vector of char + + // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene. + tinyusdz::USDZAsset usdz_asset; + if (is_usdz) { + bool is_read_USDZ_asset = is_load_from_mem ? tinyusdz::ReadUSDZAssetInfoFromMemory(in_mem_data.data(), in_mem_data.size(), false, &usdz_asset, &warn, &err) : + tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err); + if (!is_read_USDZ_asset) { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + + tinyusdz::AssetResolutionResolver arr; + if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset)) { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + env.asset_resolver = arr; + } + } + + ret = converter.ConvertToRenderScene(env, &render_scene); + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ConvertToRenderScene() failed!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + + // sanityCheckNodesRecursive(pScene->mRootNode); + animations(render_scene, pScene); + meshes(render_scene, pScene, nameWExt); + materials(render_scene, pScene, nameWExt); + textures(render_scene, pScene, nameWExt); + textureImages(render_scene, pScene, nameWExt); + buffers(render_scene, pScene, nameWExt); + + setupNodes(render_scene, pScene, nameWExt); + + setupBlendShapes(render_scene, pScene, nameWExt); +} +void USDImporterImplTinyusdz::animations( + const tinyusdz::tydra::RenderScene& render_scene, + aiScene* pScene) { + if (render_scene.animations.empty()) { + return; + } + + pScene->mNumAnimations = render_scene.animations.size(); + pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations]; + + for (int animationIndex = 0; animationIndex < pScene->mNumAnimations; ++animationIndex) { + + const auto &animation = render_scene.animations[animationIndex]; + + auto newAiAnimation = new aiAnimation(); + pScene->mAnimations[animationIndex] = newAiAnimation; + + newAiAnimation->mName = animation.abs_path; + + if (animation.channels_map.empty()) { + newAiAnimation->mNumChannels = 0; + continue; + } + + // each channel affects a node (joint) + newAiAnimation->mNumChannels = animation.channels_map.size(); + newAiAnimation->mChannels = new aiNodeAnim *[newAiAnimation->mNumChannels]; + int channelIndex = 0; + for (const auto &[jointName, animationChannelMap] : animation.channels_map) { + auto newAiNodeAnim = new aiNodeAnim(); + newAiAnimation->mChannels[channelIndex] = newAiNodeAnim; + newAiNodeAnim->mNodeName = jointName; + newAiAnimation->mDuration = 0; + + std::vector positionKeys; + std::vector rotationKeys; + std::vector scalingKeys; + + for (const auto &[channelType, animChannel] : animationChannelMap) { + switch (channelType) { + case tinyusdz::tydra::AnimationChannel::ChannelType::Rotation: + if (animChannel.rotations.static_value.has_value()) { + rotationKeys.emplace_back(0, tinyUsdzQuatToAiQuat(animChannel.rotations.static_value.value())); + } + for (const auto &rotationAnimSampler : animChannel.rotations.samples) { + if (rotationAnimSampler.t > newAiAnimation->mDuration) { + newAiAnimation->mDuration = rotationAnimSampler.t; + } + + rotationKeys.emplace_back(rotationAnimSampler.t, tinyUsdzQuatToAiQuat(rotationAnimSampler.value)); + } + break; + case tinyusdz::tydra::AnimationChannel::ChannelType::Scale: + if (animChannel.scales.static_value.has_value()) { + scalingKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.scales.static_value.value())); + } + for (const auto &scaleAnimSampler : animChannel.scales.samples) { + if (scaleAnimSampler.t > newAiAnimation->mDuration) { + newAiAnimation->mDuration = scaleAnimSampler.t; + } + scalingKeys.emplace_back(scaleAnimSampler.t, tinyUsdzScaleOrPosToAssimp(scaleAnimSampler.value)); + } + break; + case tinyusdz::tydra::AnimationChannel::ChannelType::Transform: + if (animChannel.transforms.static_value.has_value()) { + aiVector3D position; + aiVector3D scale; + aiQuaternion rotation; + tinyUsdzMat4ToAiMat4(animChannel.transforms.static_value.value().m).Decompose(scale, rotation, position); + + positionKeys.emplace_back(0, position); + scalingKeys.emplace_back(0, scale); + rotationKeys.emplace_back(0, rotation); + } + for (const auto &transformAnimSampler : animChannel.transforms.samples) { + if (transformAnimSampler.t > newAiAnimation->mDuration) { + newAiAnimation->mDuration = transformAnimSampler.t; + } + + aiVector3D position; + aiVector3D scale; + aiQuaternion rotation; + tinyUsdzMat4ToAiMat4(transformAnimSampler.value.m).Decompose(scale, rotation, position); + + positionKeys.emplace_back(transformAnimSampler.t, position); + scalingKeys.emplace_back(transformAnimSampler.t, scale); + rotationKeys.emplace_back(transformAnimSampler.t, rotation); + } + break; + case tinyusdz::tydra::AnimationChannel::ChannelType::Translation: + if (animChannel.translations.static_value.has_value()) { + positionKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.translations.static_value.value())); + } + for (const auto &translationAnimSampler : animChannel.translations.samples) { + if (translationAnimSampler.t > newAiAnimation->mDuration) { + newAiAnimation->mDuration = translationAnimSampler.t; + } + + positionKeys.emplace_back(translationAnimSampler.t, tinyUsdzScaleOrPosToAssimp(translationAnimSampler.value)); + } + break; + default: + TINYUSDZLOGW(TAG, "Unsupported animation channel type (%s). Please update the USD importer to support this animation channel.", tinyusdzAnimChannelTypeFor(channelType).c_str()); + } + } + + newAiNodeAnim->mNumPositionKeys = positionKeys.size(); + newAiNodeAnim->mPositionKeys = new aiVectorKey[newAiNodeAnim->mNumPositionKeys]; + std::move(positionKeys.begin(), positionKeys.end(), newAiNodeAnim->mPositionKeys); + + newAiNodeAnim->mNumRotationKeys = rotationKeys.size(); + newAiNodeAnim->mRotationKeys = new aiQuatKey[newAiNodeAnim->mNumRotationKeys]; + std::move(rotationKeys.begin(), rotationKeys.end(), newAiNodeAnim->mRotationKeys); + + newAiNodeAnim->mNumScalingKeys = scalingKeys.size(); + newAiNodeAnim->mScalingKeys = new aiVectorKey[newAiNodeAnim->mNumScalingKeys]; + std::move(scalingKeys.begin(), scalingKeys.end(), newAiNodeAnim->mScalingKeys); + + ++channelIndex; + } + } +} + +void USDImporterImplTinyusdz::meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + pScene->mNumMeshes = static_cast(render_scene.meshes.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + ss.str(""); + ss << "meshes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + // Export meshes + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mMeshes[meshIdx] = new aiMesh(); + pScene->mMeshes[meshIdx]->mName.Set(render_scene.meshes[meshIdx].prim_name); + ss.str(""); + ss << " mesh[" << meshIdx << "]: " << + render_scene.meshes[meshIdx].joint_and_weights.jointIndices.size() << " jointIndices, " << + render_scene.meshes[meshIdx].joint_and_weights.jointWeights.size() << " jointWeights, elementSize: " << + render_scene.meshes[meshIdx].joint_and_weights.elementSize; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ss.str(""); + ss << " skel_id: " << render_scene.meshes[meshIdx].skel_id; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (render_scene.meshes[meshIdx].material_id > -1) { + pScene->mMeshes[meshIdx]->mMaterialIndex = render_scene.meshes[meshIdx].material_id; + } + verticesForMesh(render_scene, pScene, meshIdx, nameWExt); + facesForMesh(render_scene, pScene, meshIdx, nameWExt); + // Some models infer normals from faces, but others need them e.g. + // - apple "toy car" canopy normals will be wrong + // - human "untitled" model (tinyusdz issue #115) will be "splotchy" + normalsForMesh(render_scene, pScene, meshIdx, nameWExt); + materialsForMesh(render_scene, pScene, meshIdx, nameWExt); + uvsForMesh(render_scene, pScene, meshIdx, nameWExt); + } +} + +void USDImporterImplTinyusdz::verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + const auto numVertices = static_cast(render_scene.meshes[meshIdx].points.size()); + pScene->mMeshes[meshIdx]->mNumVertices = numVertices; + pScene->mMeshes[meshIdx]->mVertices = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + + // Check if this is a skinned mesh + if (int skeleton_id = render_scene.meshes[meshIdx].skel_id; skeleton_id > -1) { + // Recursively iterate to collect all the joints in the hierarchy into a flattened array + std::vector skeletonNodes; + skeletonNodes.push_back(&render_scene.skeletons[skeleton_id].root_node); + for (int i = 0; i < skeletonNodes.size(); ++i) { + for (const auto &child : skeletonNodes[i]->children) { + skeletonNodes.push_back(&child); + } + } + + // Convert USD skeleton joints to Assimp bones + const unsigned int numBones = skeletonNodes.size(); + pScene->mMeshes[meshIdx]->mNumBones = numBones; + pScene->mMeshes[meshIdx]->mBones = new aiBone *[numBones]; + + for (unsigned int i = 0; i < numBones; ++i) { + const tinyusdz::tydra::SkelNode *skeletonNode = skeletonNodes[i]; + const int boneIndex = skeletonNode->joint_id; + + // Sorted so that Assimp bone ids align with USD joint id + auto outputBone = new aiBone(); + outputBone->mName = aiString(skeletonNode->joint_name); + outputBone->mOffsetMatrix = tinyUsdzMat4ToAiMat4(skeletonNode->bind_transform.m).Inverse(); + pScene->mMeshes[meshIdx]->mBones[boneIndex] = outputBone; + } + + // Vertex weights + std::vector> aiBonesVertexWeights; + aiBonesVertexWeights.resize(numBones); + + const std::vector &jointIndices = render_scene.meshes[meshIdx].joint_and_weights.jointIndices; + const std::vector &jointWeightIndices = render_scene.meshes[meshIdx].joint_and_weights.jointWeights; + const int numWeightsPerVertex = render_scene.meshes[meshIdx].joint_and_weights.elementSize; + + for (unsigned int vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex) { + for (int weightIndex = 0; weightIndex < numWeightsPerVertex; ++weightIndex) { + const unsigned int index = vertexIndex * numWeightsPerVertex + weightIndex; + const float jointWeight = jointWeightIndices[index]; + + if (jointWeight > 0) { + const int jointIndex = jointIndices[index]; + aiBonesVertexWeights[jointIndex].emplace_back(vertexIndex, jointWeight); + } + } + } + + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + const unsigned int numWeightsForBone = aiBonesVertexWeights[boneIndex].size(); + pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights = new aiVertexWeight[numWeightsForBone]; + pScene->mMeshes[meshIdx]->mBones[boneIndex]->mNumWeights = numWeightsForBone; + + std::swap_ranges(aiBonesVertexWeights[boneIndex].begin(), aiBonesVertexWeights[boneIndex].end(), pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights); + } + } // Skinned mesh end + + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mNumVertices; ++j) { + pScene->mMeshes[meshIdx]->mVertices[j].x = render_scene.meshes[meshIdx].points[j][0]; + pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1]; + pScene->mMeshes[meshIdx]->mVertices[j].z = render_scene.meshes[meshIdx].points[j][2]; + } +} + +void USDImporterImplTinyusdz::facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + pScene->mMeshes[meshIdx]->mNumFaces = static_cast(render_scene.meshes[meshIdx].faceVertexCounts().size()); + pScene->mMeshes[meshIdx]->mFaces = new aiFace[pScene->mMeshes[meshIdx]->mNumFaces](); + size_t faceVertIdxOffset = 0; + for (size_t faceIdx = 0; faceIdx < pScene->mMeshes[meshIdx]->mNumFaces; ++faceIdx) { + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices = render_scene.meshes[meshIdx].faceVertexCounts()[faceIdx]; + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices = new unsigned int[pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices]; + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; ++j) { + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices[j] = + render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; + } + faceVertIdxOffset += pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; + } +} + +void USDImporterImplTinyusdz::normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + pScene->mMeshes[meshIdx]->mNormals = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + const float *floatPtr = reinterpret_cast(render_scene.meshes[meshIdx].normals.get_data().data()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 3) { + pScene->mMeshes[meshIdx]->mNormals[vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].y = floatPtr[fpj + 1]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].z = floatPtr[fpj + 2]; + } +} + +void USDImporterImplTinyusdz::materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(render_scene); UNUSED(pScene); UNUSED(meshIdx); UNUSED(nameWExt); +} + +void USDImporterImplTinyusdz::uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + const size_t uvSlotsCount = render_scene.meshes[meshIdx].texcoords.size(); + if (uvSlotsCount < 1) { + return; + } + pScene->mMeshes[meshIdx]->mTextureCoords[0] = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + pScene->mMeshes[meshIdx]->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D. + for (unsigned int uvSlotIdx = 0; uvSlotIdx < uvSlotsCount; ++uvSlotIdx) { + const auto uvsForSlot = render_scene.meshes[meshIdx].texcoords.at(uvSlotIdx); + if (uvsForSlot.get_data().size() == 0) { + continue; + } + const float *floatPtr = reinterpret_cast(uvsForSlot.get_data().data()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 2) { + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].y = floatPtr[fpj + 1]; + } + } +} + +static aiColor3D *ownedColorPtrFor(const std::array &color) { + aiColor3D *colorPtr = new aiColor3D(); + colorPtr->r = color[0]; + colorPtr->g = color[1]; + colorPtr->b = color[2]; + return colorPtr; +} + +static std::string nameForTextureWithId( + const tinyusdz::tydra::RenderScene &render_scene, + const int targetId) { + stringstream ss; + std::string texName; + for (const auto &image : render_scene.images) { + if (image.buffer_id == targetId) { + texName = image.asset_identifier; + ss.str(""); + ss << "nameForTextureWithId(): found texture " << texName << " with target id " << targetId; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + break; + } + } + ss.str(""); + ss << "nameForTextureWithId(): ERROR! Failed to find texture with target id " << targetId; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return texName; +} + +static void assignTexture( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::RenderMaterial &material, + aiMaterial *mat, + const int textureId, + const int aiTextureType) { + UNUSED(material); + std::string name = nameForTextureWithId(render_scene, textureId); + aiString *texName = new aiString(); + texName->Set(name); + stringstream ss; + ss.str(""); + ss << "assignTexture(): name: " << name; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + // TODO: verify hard-coded '0' index is correct + mat->AddProperty(texName, _AI_MATKEY_TEXTURE_BASE, aiTextureType, 0); +} + +void USDImporterImplTinyusdz::materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numMaterials{render_scene.materials.size()}; + (void) numMaterials; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "materials(): model" << nameWExt << ", numMaterials: " << numMaterials; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mNumMaterials = 0; + if (render_scene.materials.empty()) { + return; + } + pScene->mMaterials = new aiMaterial *[render_scene.materials.size()]; + for (const auto &material : render_scene.materials) { + ss.str(""); + ss << " material[" << pScene->mNumMaterials << "]: name: |" << material.name << "|, disp name: |" << material.display_name << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + aiMaterial *mat = new aiMaterial; + + aiString *materialName = new aiString(); + materialName->Set(material.name); + mat->AddProperty(materialName, AI_MATKEY_NAME); + + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.diffuseColor.value), + 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.specularColor.value), + 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.emissiveColor.value), + 1, AI_MATKEY_COLOR_EMISSIVE); + + ss.str(""); + if (material.surfaceShader.diffuseColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.diffuseColor.texture_id, aiTextureType_DIFFUSE); + ss << " material[" << pScene->mNumMaterials << "]: diff tex id " << material.surfaceShader.diffuseColor.texture_id << "\n"; + } + if (material.surfaceShader.specularColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.specularColor.texture_id, aiTextureType_SPECULAR); + ss << " material[" << pScene->mNumMaterials << "]: spec tex id " << material.surfaceShader.specularColor.texture_id << "\n"; + } + if (material.surfaceShader.normal.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.normal.texture_id, aiTextureType_NORMALS); + ss << " material[" << pScene->mNumMaterials << "]: normal tex id " << material.surfaceShader.normal.texture_id << "\n"; + } + if (material.surfaceShader.emissiveColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.emissiveColor.texture_id, aiTextureType_EMISSIVE); + ss << " material[" << pScene->mNumMaterials << "]: emissive tex id " << material.surfaceShader.emissiveColor.texture_id << "\n"; + } + if (material.surfaceShader.occlusion.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.occlusion.texture_id, aiTextureType_LIGHTMAP); + ss << " material[" << pScene->mNumMaterials << "]: lightmap (occlusion) tex id " << material.surfaceShader.occlusion.texture_id << "\n"; + } + if (material.surfaceShader.metallic.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.metallic.texture_id, aiTextureType_METALNESS); + ss << " material[" << pScene->mNumMaterials << "]: metallic tex id " << material.surfaceShader.metallic.texture_id << "\n"; + } + if (material.surfaceShader.roughness.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.roughness.texture_id, aiTextureType_DIFFUSE_ROUGHNESS); + ss << " material[" << pScene->mNumMaterials << "]: roughness tex id " << material.surfaceShader.roughness.texture_id << "\n"; + } + if (material.surfaceShader.clearcoat.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.clearcoat.texture_id, aiTextureType_CLEARCOAT); + ss << " material[" << pScene->mNumMaterials << "]: clearcoat tex id " << material.surfaceShader.clearcoat.texture_id << "\n"; + } + if (material.surfaceShader.opacity.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.opacity.texture_id, aiTextureType_OPACITY); + ss << " material[" << pScene->mNumMaterials << "]: opacity tex id " << material.surfaceShader.opacity.texture_id << "\n"; + } + if (material.surfaceShader.displacement.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.displacement.texture_id, aiTextureType_DISPLACEMENT); + ss << " material[" << pScene->mNumMaterials << "]: displacement tex id " << material.surfaceShader.displacement.texture_id << "\n"; + } + if (material.surfaceShader.clearcoatRoughness.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: clearcoatRoughness tex id " << material.surfaceShader.clearcoatRoughness.texture_id << "\n"; + } + if (material.surfaceShader.opacityThreshold.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: opacityThreshold tex id " << material.surfaceShader.opacityThreshold.texture_id << "\n"; + } + if (material.surfaceShader.ior.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: ior tex id " << material.surfaceShader.ior.texture_id << "\n"; + } + if (!ss.str().empty()) { + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + + pScene->mMaterials[pScene->mNumMaterials] = mat; + ++pScene->mNumMaterials; + } +} + +void USDImporterImplTinyusdz::textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + UNUSED(pScene); + const size_t numTextures{render_scene.textures.size()}; + UNUSED(numTextures); // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "textures(): model" << nameWExt << ", numTextures: " << numTextures; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i{0}; + UNUSED(i); + for (const auto &texture : render_scene.textures) { + UNUSED(texture); + ss.str(""); + ss << " texture[" << i << "]: id: " << texture.texture_image_id << ", disp name: |" << texture.display_name << "|, varname_uv: " << + texture.varname_uv << ", prim_name: |" << texture.prim_name << "|, abs_path: |" << texture.abs_path << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +/** + * "owned" as in, used "new" to allocate and aiScene now responsible for "delete" + * + * @param render_scene renderScene object + * @param image textureImage object + * @param nameWExt filename w/ext (use to extract file type hint) + * @return aiTexture ptr + */ +static aiTexture *ownedEmbeddedTextureFor( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::TextureImage &image, + const std::string &nameWExt) { + UNUSED(nameWExt); + stringstream ss; + aiTexture *tex = new aiTexture(); + size_t pos = image.asset_identifier.find_last_of('/'); + string embTexName{image.asset_identifier.substr(pos + 1)}; + tex->mFilename.Set(image.asset_identifier.c_str()); + tex->mHeight = image.height; +// const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size() / image.channels}; + tex->mWidth = image.width; + if (tex->mHeight == 0) { + pos = embTexName.find_last_of('.'); + strncpy(tex->achFormatHint, embTexName.substr(pos + 1).c_str(), 3); + const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size()}; + tex->pcData = (aiTexel *) new char[imageBytesCount]; + memcpy(tex->pcData, &render_scene.buffers[image.buffer_id].data[0], imageBytesCount); + } else { + string formatHint{"rgba8888"}; + strncpy(tex->achFormatHint, formatHint.c_str(), 8); + const size_t imageTexelsCount{tex->mWidth * tex->mHeight}; + tex->pcData = (aiTexel *) new char[imageTexelsCount * image.channels]; + const float *floatPtr = reinterpret_cast(&render_scene.buffers[image.buffer_id].data[0]); + ss.str(""); + ss << "ownedEmbeddedTextureFor(): manual fill..."; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t i = 0, fpi = 0; i < imageTexelsCount; ++i, fpi += 4) { + tex->pcData[i].b = static_cast(floatPtr[fpi] * 255); + tex->pcData[i].g = static_cast(floatPtr[fpi + 1] * 255); + tex->pcData[i].r = static_cast(floatPtr[fpi + 2] * 255); + tex->pcData[i].a = static_cast(floatPtr[fpi + 3] * 255); + } + ss.str(""); + ss << "ownedEmbeddedTextureFor(): imageTexelsCount: " << imageTexelsCount << ", channels: " << image.channels; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + return tex; +} + +void USDImporterImplTinyusdz::textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + const size_t numTextureImages{render_scene.images.size()}; + UNUSED(numTextureImages); // Ignore unused variable when -Werror enabled + ss.str(""); + ss << "textureImages(): model" << nameWExt << ", numTextureImages: " << numTextureImages; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = nullptr; // Need to iterate over images before knowing if valid textures available + pScene->mNumTextures = 0; + for (const auto &image : render_scene.images) { + ss.str(""); + ss << " image[" << pScene->mNumTextures << "]: |" << image.asset_identifier << "| w: " << image.width << ", h: " << image.height << + ", channels: " << image.channels << ", miplevel: " << image.miplevel << ", buffer id: " << image.buffer_id << "\n" << + " buffers.size(): " << render_scene.buffers.size() << ", data empty? " << render_scene.buffers[image.buffer_id].data.empty(); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (image.buffer_id > -1 && + image.buffer_id < static_cast(render_scene.buffers.size()) && + !render_scene.buffers[image.buffer_id].data.empty()) { + aiTexture *tex = ownedEmbeddedTextureFor( + render_scene, + image, + nameWExt); + if (pScene->mTextures == nullptr) { + ss.str(""); + ss << " Init pScene->mTextures[" << render_scene.images.size() << "]"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = new aiTexture *[render_scene.images.size()]; + } + ss.str(""); + ss << " pScene->mTextures[" << pScene->mNumTextures << "] name: |" << tex->mFilename.C_Str() << + "|, w: " << tex->mWidth << ", h: " << tex->mHeight << ", hint: " << tex->achFormatHint; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures[pScene->mNumTextures++] = tex; + } + } +} + +void USDImporterImplTinyusdz::buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numBuffers{render_scene.buffers.size()}; + UNUSED(pScene); UNUSED(numBuffers); // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "buffers(): model" << nameWExt << ", numBuffers: " << numBuffers; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i = 0; + for (const auto &buffer : render_scene.buffers) { + ss.str(""); + ss << " buffer[" << i << "]: count: " << buffer.data.size() << ", type: " << to_string(buffer.componentType); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +void USDImporterImplTinyusdz::setupNodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + + pScene->mRootNode = nodes(render_scene, nameWExt); + if (pScene->mRootNode == nullptr) { + return; + } + + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; + + ss.str(""); + ss << "setupNodes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + ss << ", mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + for (unsigned int meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mRootNode->mMeshes[meshIdx] = meshIdx; + } +} + +aiNode *USDImporterImplTinyusdz::nodes( + const tinyusdz::tydra::RenderScene &render_scene, + const std::string &nameWExt) { + const size_t numNodes{render_scene.nodes.size()}; + (void) numNodes; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + aiNode *rootNode = nodesRecursive(nullptr, render_scene.nodes[0], render_scene.skeletons); + return rootNode; +} + +using Assimp::tinyusdzNodeTypeFor; +using Assimp::tinyUsdzMat4ToAiMat4; +using tinyusdz::tydra::NodeType; +aiNode *USDImporterImplTinyusdz::nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + const std::vector &skeletons) { + stringstream ss; + aiNode *cNode = new aiNode(); + cNode->mParent = pNodeParent; + cNode->mName.Set(node.prim_name); + cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m); + ss.str(""); + ss << "nodesRecursive(): node " << cNode->mName.C_Str() << + " type: |" << tinyusdzNodeTypeFor(node.nodeType) << + "|, disp " << node.display_name << ", abs " << node.abs_path; + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << node.children.size() << " children"; + if (node.id != -1) { + ss << "\n node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")"; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + unsigned int numChildren = node.children.size(); + + // Find any tinyusdz skeletons which might begin at this node + // Add the skeleton bones as child nodes + const tinyusdz::tydra::SkelNode *skelNode = nullptr; + for (const auto &skeleton : skeletons) { + if (skeleton.abs_path == node.abs_path) { + // Add this skeleton's bones as child nodes + ++numChildren; + skelNode = &skeleton.root_node; + break; + } + } + + cNode->mNumChildren = numChildren; + + // Done. No more children. + if (numChildren == 0) { + return cNode; + } + + cNode->mChildren = new aiNode *[cNode->mNumChildren]; + + size_t i{ 0 }; + for (const auto &childNode : node.children) { + cNode->mChildren[i] = nodesRecursive(cNode, childNode, skeletons); + ++i; + } + + if (skelNode != nullptr) { + // Convert USD skeleton into an Assimp node and make it the last child + cNode->mChildren[cNode->mNumChildren-1] = skeletonNodesRecursive(cNode, *skelNode); + } + + return cNode; +} + +aiNode *USDImporterImplTinyusdz::skeletonNodesRecursive( + aiNode* pNodeParent, + const tinyusdz::tydra::SkelNode& joint) { + auto *cNode = new aiNode(joint.joint_path); + cNode->mParent = pNodeParent; + cNode->mNumMeshes = 0; // not a mesh node + cNode->mTransformation = tinyUsdzMat4ToAiMat4(joint.rest_transform.m); + + // Done. No more children. + if (joint.children.empty()) { + return cNode; + } + + cNode->mNumChildren = static_cast(joint.children.size()); + cNode->mChildren = new aiNode *[cNode->mNumChildren]; + + for (int i = 0; i < cNode->mNumChildren; ++i) { + const tinyusdz::tydra::SkelNode &childJoint = joint.children[i]; + cNode->mChildren[i] = skeletonNodesRecursive(cNode, childJoint); + } + + return cNode; +} + +void USDImporterImplTinyusdz::sanityCheckNodesRecursive( + aiNode *cNode) { + stringstream ss; + ss.str(""); + ss << "sanityCheckNodesRecursive(): node " << cNode->mName.C_Str(); + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << cNode->mNumChildren << " children"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t i = 0; i < cNode->mNumChildren; ++i) { + sanityCheckNodesRecursive(cNode->mChildren[i]); + } +} + +void USDImporterImplTinyusdz::setupBlendShapes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + ss.str(""); + ss << "setupBlendShapes(): iterating over " << pScene->mNumMeshes << " meshes for model" << nameWExt; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + blendShapesForMesh(render_scene, pScene, meshIdx, nameWExt); + } +} + +void USDImporterImplTinyusdz::blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + stringstream ss; + const unsigned int numBlendShapeTargets{static_cast(render_scene.meshes[meshIdx].targets.size())}; + UNUSED(numBlendShapeTargets); // Ignore unused variable when -Werror enabled + ss.str(""); + ss << " blendShapesForMesh(): mesh[" << meshIdx << "], numBlendShapeTargets: " << numBlendShapeTargets; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (numBlendShapeTargets > 0) { + pScene->mMeshes[meshIdx]->mNumAnimMeshes = numBlendShapeTargets; + pScene->mMeshes[meshIdx]->mAnimMeshes = new aiAnimMesh *[pScene->mMeshes[meshIdx]->mNumAnimMeshes]; + } + auto mapIter = render_scene.meshes[meshIdx].targets.begin(); + size_t animMeshIdx{0}; + for (; mapIter != render_scene.meshes[meshIdx].targets.end(); ++mapIter) { + const std::string name{mapIter->first}; + const tinyusdz::tydra::ShapeTarget shapeTarget{mapIter->second}; + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx] = aiCreateAnimMesh(pScene->mMeshes[meshIdx]); + ss.str(""); + ss << " mAnimMeshes[" << animMeshIdx << "]: mNumVertices: " << pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNumVertices << + ", target: " << shapeTarget.pointIndices.size() << " pointIndices, " << shapeTarget.pointOffsets.size() << + " pointOffsets, " << shapeTarget.normalOffsets.size() << " normalOffsets"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t iVert = 0; iVert < shapeTarget.pointOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mVertices[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.pointOffsets[iVert]); + } + for (size_t iVert = 0; iVert < shapeTarget.normalOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNormals[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.normalOffsets[iVert]); + } + ss.str(""); + ss << " target[" << animMeshIdx << "]: name: " << name << ", prim_name: " << + shapeTarget.prim_name << ", abs_path: " << shapeTarget.abs_path << + ", display_name: " << shapeTarget.display_name << ", " << shapeTarget.pointIndices.size() << + " pointIndices, " << shapeTarget.pointOffsets.size() << " pointOffsets, " << + shapeTarget.normalOffsets.size() << " normalOffsets, " << shapeTarget.inbetweens.size() << + " inbetweens"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++animMeshIdx; + } +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.h b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.h new file mode 100644 index 000000000..601ddc066 --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdz.h @@ -0,0 +1,160 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED + +#include +#include +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" + +namespace Assimp { +class USDImporterImplTinyusdz { +public: + USDImporterImplTinyusdz() = default; + ~USDImporterImplTinyusdz() = default; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler); + + void animations( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene); + + void meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void setupNodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + aiNode *nodes( + const tinyusdz::tydra::RenderScene &render_scene, + const std::string &nameWExt); + + aiNode *nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + const std::vector &skeletons); + + aiNode *skeletonNodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::SkelNode &joint); + + void sanityCheckNodesRecursive( + aiNode *pNode); + + void setupBlendShapes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); +}; +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp new file mode 100644 index 000000000..6708d7972 --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp @@ -0,0 +1,114 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include "USDLoaderImplTinyusdzHelper.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { +//const char *const TAG = "tinyusdz helper"; +} + +using ChannelType = tinyusdz::tydra::AnimationChannel::ChannelType; +std::string Assimp::tinyusdzAnimChannelTypeFor(ChannelType animChannel) { + switch (animChannel) { + case ChannelType::Transform: { + return "Transform"; + } + case ChannelType::Translation: { + return "Translation"; + } + case ChannelType::Rotation: { + return "Rotation"; + } + case ChannelType::Scale: { + return "Scale"; + } + case ChannelType::Weight: { + return "Weight"; + } + default: + return "Invalid"; + } +} + +using tinyusdz::tydra::NodeType; +std::string Assimp::tinyusdzNodeTypeFor(NodeType type) { + switch (type) { + case NodeType::Xform: { + return "Xform"; + } + case NodeType::Mesh: { + return "Mesh"; + } + case NodeType::Camera: { + return "Camera"; + } + case NodeType::Skeleton: { + return "Skeleton"; + } + case NodeType::PointLight: { + return "PointLight"; + } + case NodeType::DirectionalLight: { + return "DirectionalLight"; + } + case NodeType::EnvmapLight: { + return "EnvmapLight"; + } + default: + return "Invalid"; + } +} + +aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn) { + return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]); +} + +aiQuaternion Assimp::tinyUsdzQuatToAiQuat(const std::array &quatIn) { + // tinyusdz "quat" is x,y,z,w + // aiQuaternion is w,x,y,z + return aiQuaternion( + quatIn[3], quatIn[0], quatIn[1], quatIn[2]); +} + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h new file mode 100644 index 000000000..42a7b9d9f --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h @@ -0,0 +1,92 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED + +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" +#include + +namespace Assimp { + +std::string tinyusdzAnimChannelTypeFor( + tinyusdz::tydra::AnimationChannel::ChannelType animChannel); +std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type); + +template +aiMatrix4x4 tinyUsdzMat4ToAiMat4(const T matIn[4][4]) { + static_assert(std::is_floating_point_v, "Only floating-point types are allowed."); + aiMatrix4x4 matOut; + matOut.a1 = matIn[0][0]; + matOut.a2 = matIn[1][0]; + matOut.a3 = matIn[2][0]; + matOut.a4 = matIn[3][0]; + matOut.b1 = matIn[0][1]; + matOut.b2 = matIn[1][1]; + matOut.b3 = matIn[2][1]; + matOut.b4 = matIn[3][1]; + matOut.c1 = matIn[0][2]; + matOut.c2 = matIn[1][2]; + matOut.c3 = matIn[2][2]; + matOut.c4 = matIn[3][2]; + matOut.d1 = matIn[0][3]; + matOut.d2 = matIn[1][3]; + matOut.d3 = matIn[2][3]; + matOut.d4 = matIn[3][3]; + return matOut; +} +aiVector3D tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn); + +/** + * Convert quaternion from tinyusdz "quat" to assimp "aiQuaternion" type + * + * @param quatIn tinyusdz float[4] in x,y,z,w order + * @return assimp aiQuaternion converted from input + */ +aiQuaternion tinyUsdzQuatToAiQuat(const std::array &quatIn); + +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.cpp b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.cpp new file mode 100644 index 000000000..8d9b22df2 --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.cpp @@ -0,0 +1,116 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoaderUtil.h" + +namespace Assimp { +using namespace std; +bool isUsda(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'a' || ext[3] == 'A'); +} + +bool isUsdc(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'c' || ext[3] == 'C'); +} + +bool isUsdz(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'z' || ext[3] == 'Z'); +} + +bool isUsd(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 3) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D'); +} +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/Engine/lib/assimp/samples/SharedCode/UTFConverter.cpp b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.h similarity index 73% rename from Engine/lib/assimp/samples/SharedCode/UTFConverter.cpp rename to Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.h index a1bff7e4b..7601cfbc1 100644 --- a/Engine/lib/assimp/samples/SharedCode/UTFConverter.cpp +++ b/Engine/lib/assimp/code/AssetLib/USD/USDLoaderUtil.h @@ -1,17 +1,14 @@ /* ---------------------------------------------------------------------------- Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - +---------------------------------------------------------------------- +Copyright (c) 2006-2024, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: +with or without modification, are permitted provided that the +following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the @@ -38,15 +35,26 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- + +---------------------------------------------------------------------- */ -#include "UTFConverter.h" +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_UTIL_H_INCLUDED +#define AI_USDLOADER_UTIL_H_INCLUDED -namespace AssimpSamples { -namespace SharedCode { +#include -typename UTFConverter::UTFConverterImpl UTFConverter::impl_; +namespace Assimp { -} -} +bool isUsda(const std::string &pFile); +bool isUsdc(const std::string &pFile); +bool isUsdz(const std::string &pFile); +bool isUsd(const std::string &pFile); + +} // namespace Assimp + +#endif // AI_USDLOADER_UTIL_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/USD/USDPreprocessor.h b/Engine/lib/assimp/code/AssetLib/USD/USDPreprocessor.h new file mode 100644 index 000000000..dec37ea27 --- /dev/null +++ b/Engine/lib/assimp/code/AssetLib/USD/USDPreprocessor.h @@ -0,0 +1,50 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDPREPROCESSOR_H_INCLUDED +#define AI_USDPREPROCESSOR_H_INCLUDED + +#define UNUSED(x) (void) x + +#endif // AI_USDPREPROCESSOR_H_INCLUDED diff --git a/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.cpp b/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.cpp index 439ac2988..85b68b508 100644 --- a/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,7 +63,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { namespace Unreal { @@ -152,7 +152,7 @@ inline void DecompressVertex(aiVector3D &v, int32_t in) { } // end namespace Unreal -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Unreal Mesh Importer", "", "", @@ -178,7 +178,7 @@ UnrealImporter::~UnrealImporter() = default; // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool UnrealImporter::CanRead(const std::string & filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { +bool UnrealImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { return SimpleExtensionCheck(filename, "3d", "uc"); } @@ -315,47 +315,49 @@ void UnrealImporter::InternReadFile(const std::string &pFile, // we can live without the uc file if necessary std::unique_ptr pb(pIOHandler->Open(uc_path)); - if (pb.get()) { + if (pb) { std::vector _data; TextFileToBuffer(pb.get(), _data); const char *data = &_data[0]; + const char *end = &_data[_data.size() - 1] + 1; std::vector> tempTextures; // do a quick search in the UC file for some known, usually texture-related, tags for (; *data; ++data) { if (TokenMatchI(data, "#exec", 5)) { - SkipSpacesAndLineEnd(&data); + SkipSpacesAndLineEnd(&data, end); // #exec TEXTURE IMPORT [...] NAME=jjjjj [...] FILE=jjjj.pcx [...] if (TokenMatchI(data, "TEXTURE", 7)) { - SkipSpacesAndLineEnd(&data); + SkipSpacesAndLineEnd(&data, end); if (TokenMatchI(data, "IMPORT", 6)) { tempTextures.emplace_back(); std::pair &me = tempTextures.back(); for (; !IsLineEnd(*data); ++data) { - if (!::ASSIMP_strincmp(data, "NAME=", 5)) { + if (!ASSIMP_strincmp(data, "NAME=", 5)) { const char *d = data += 5; for (; !IsSpaceOrNewLine(*data); ++data) ; me.first = std::string(d, (size_t)(data - d)); - } else if (!::ASSIMP_strincmp(data, "FILE=", 5)) { + } else if (!ASSIMP_strincmp(data, "FILE=", 5)) { const char *d = data += 5; for (; !IsSpaceOrNewLine(*data); ++data) ; me.second = std::string(d, (size_t)(data - d)); } } - if (!me.first.length() || !me.second.length()) + if (!me.first.length() || !me.second.length()) { tempTextures.pop_back(); + } } } // #exec MESHMAP SETTEXTURE MESHMAP=box NUM=1 TEXTURE=Jtex1 // #exec MESHMAP SCALE MESHMAP=box X=0.1 Y=0.1 Z=0.2 else if (TokenMatchI(data, "MESHMAP", 7)) { - SkipSpacesAndLineEnd(&data); + SkipSpacesAndLineEnd(&data, end); if (TokenMatchI(data, "SETTEXTURE", 10)) { @@ -363,14 +365,13 @@ void UnrealImporter::InternReadFile(const std::string &pFile, std::pair &me = textures.back(); for (; !IsLineEnd(*data); ++data) { - if (!::ASSIMP_strincmp(data, "NUM=", 4)) { + if (!ASSIMP_strincmp(data, "NUM=", 4)) { data += 4; me.first = strtoul10(data, &data); - } else if (!::ASSIMP_strincmp(data, "TEXTURE=", 8)) { + } else if (!ASSIMP_strincmp(data, "TEXTURE=", 8)) { data += 8; const char *d = data; - for (; !IsSpaceOrNewLine(*data); ++data) - ; + for (; !IsSpaceOrNewLine(*data); ++data); me.second = std::string(d, (size_t)(data - d)); // try to find matching path names, doesn't care if we don't find them @@ -408,7 +409,7 @@ void UnrealImporter::InternReadFile(const std::string &pFile, // find out how many output meshes and materials we'll have and build material indices for (Unreal::Triangle &tri : triangles) { Unreal::TempMat mat(tri); - std::vector::iterator nt = std::find(materials.begin(), materials.end(), mat); + auto nt = std::find(materials.begin(), materials.end(), mat); if (nt == materials.end()) { // add material tri.matIndex = static_cast(materials.size()); @@ -451,7 +452,7 @@ void UnrealImporter::InternReadFile(const std::string &pFile, aiColor3D color(1.f, 1.f, 1.f); aiString s; - ::ai_snprintf(s.data, MAXLEN, "mat%u_tx%u_", i, materials[i].tex); + ::ai_snprintf(s.data, AI_MAXLEN, "mat%u_tx%u_", i, materials[i].tex); // set the two-sided flag if (materials[i].type == Unreal::MF_NORMAL_TS) { @@ -471,7 +472,7 @@ void UnrealImporter::InternReadFile(const std::string &pFile, // a special name for the weapon attachment point if (materials[i].type == Unreal::MF_WEAPON_PLACEHOLDER) { - s.length = ::ai_snprintf(s.data, MAXLEN, "$WeaponTag$"); + s.length = ::ai_snprintf(s.data, AI_MAXLEN, "$WeaponTag$"); color = aiColor3D(0.f, 0.f, 0.f); } @@ -516,4 +517,6 @@ void UnrealImporter::InternReadFile(const std::string &pFile, flipper.Execute(pScene); } +} // namespace Assimp + #endif // !! ASSIMP_BUILD_NO_3D_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.h b/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.h index a931a86dd..b32a5fc74 100644 --- a/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.h +++ b/Engine/lib/assimp/code/AssetLib/Unreal/UnrealLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,7 +56,7 @@ namespace Assimp { class UnrealImporter : public BaseImporter { public: UnrealImporter(); - ~UnrealImporter(); + ~UnrealImporter() override; // ------------------------------------------------------------------- /** @brief Returns whether we can handle the format of the given file diff --git a/Engine/lib/assimp/code/AssetLib/X/XFileExporter.cpp b/Engine/lib/assimp/code/AssetLib/X/XFileExporter.cpp index f0b1608c1..15ecf9464 100644 --- a/Engine/lib/assimp/code/AssetLib/X/XFileExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/X/XFileExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/X/XFileExporter.h b/Engine/lib/assimp/code/AssetLib/X/XFileExporter.h index 1d9a5ae77..744944062 100644 --- a/Engine/lib/assimp/code/AssetLib/X/XFileExporter.h +++ b/Engine/lib/assimp/code/AssetLib/X/XFileExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/X/XFileHelper.h b/Engine/lib/assimp/code/AssetLib/X/XFileHelper.h index 3830eb483..e3fff2b66 100644 --- a/Engine/lib/assimp/code/AssetLib/X/XFileHelper.h +++ b/Engine/lib/assimp/code/AssetLib/X/XFileHelper.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -67,7 +67,6 @@ struct TexEntry { bool mIsNormalMap; // true if the texname was specified in a NormalmapFilename tag TexEntry() AI_NO_EXCEPT : - mName(), mIsNormalMap(false) { // empty } @@ -128,17 +127,8 @@ struct Mesh { explicit Mesh(const std::string &pName = std::string()) AI_NO_EXCEPT : mName(pName), - mPositions(), - mPosFaces(), - mNormals(), - mNormFaces(), mNumTextures(0), - mTexCoords{}, - mNumColorSets(0), - mColors{}, - mFaceMaterials(), - mMaterials(), - mBones() { + mNumColorSets(0) { // empty } }; @@ -152,15 +142,12 @@ struct Node { std::vector mMeshes; Node() AI_NO_EXCEPT - : mName(), - mTrafoMatrix(), - mParent(nullptr), - mChildren(), - mMeshes() { + : mTrafoMatrix(), + mParent(nullptr) { // empty } explicit Node(Node *pParent) : - mName(), mTrafoMatrix(), mParent(pParent), mChildren(), mMeshes() { + mTrafoMatrix(), mParent(pParent) { // empty } @@ -211,8 +198,6 @@ struct Scene { Scene() AI_NO_EXCEPT : mRootNode(nullptr), - mGlobalMeshes(), - mGlobalMaterials(), mAnimTicksPerSecond(0) { // empty } diff --git a/Engine/lib/assimp/code/AssetLib/X/XFileImporter.cpp b/Engine/lib/assimp/code/AssetLib/X/XFileImporter.cpp index 83e9a74f2..b386ff959 100644 --- a/Engine/lib/assimp/code/AssetLib/X/XFileImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/X/XFileImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,10 +57,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { + using namespace Assimp::Formatter; -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "Direct3D XFile Importer", "", "", @@ -73,149 +74,137 @@ static const aiImporterDesc desc = { "x" }; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -XFileImporter::XFileImporter() -: mBuffer() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -XFileImporter::~XFileImporter() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const { +bool XFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { static const uint32_t token[] = { AI_MAKE_MAGIC("xof ") }; - return CheckMagicToken(pIOHandler,pFile,token,AI_COUNT_OF(token)); + return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token)); } // ------------------------------------------------------------------------------------------------ // Get file extension list -const aiImporterDesc* XFileImporter::GetInfo () const { +const aiImporterDesc *XFileImporter::GetInfo() const { return &desc; } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { +void XFileImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { // read file into memory - std::unique_ptr file( pIOHandler->Open( pFile)); - if ( file.get() == nullptr ) { - throw DeadlyImportError( "Failed to open file ", pFile, "." ); + std::unique_ptr file(pIOHandler->Open(pFile)); + if (file == nullptr) { + throw DeadlyImportError("Failed to open file ", pFile, "."); } static const size_t MinSize = 16; size_t fileSize = file->FileSize(); - if ( fileSize < MinSize ) { - throw DeadlyImportError( "XFile is too small." ); + if (fileSize < MinSize) { + throw DeadlyImportError("XFile is too small."); } // in the hope that binary files will never start with a BOM ... - mBuffer.resize( fileSize + 1); - file->Read( &mBuffer.front(), 1, fileSize); + mBuffer.resize(fileSize + 1); + file->Read(&mBuffer.front(), 1, fileSize); ConvertToUTF8(mBuffer); // parse the file into a temporary representation - XFileParser parser( mBuffer); + XFileParser parser(mBuffer); // and create the proper return structures out of it - CreateDataRepresentationFromImport( pScene, parser.GetImportedData()); + CreateDataRepresentationFromImport(pScene, parser.GetImportedData()); // if nothing came from it, report it as error - if ( !pScene->mRootNode ) { - throw DeadlyImportError( "XFile is ill-formatted - no content imported." ); + if (!pScene->mRootNode) { + throw DeadlyImportError("XFile is ill-formatted - no content imported."); } } // ------------------------------------------------------------------------------------------------ // Constructs the return data structure out of the imported data. -void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData) -{ +void XFileImporter::CreateDataRepresentationFromImport(aiScene *pScene, XFile::Scene *pData) { // Read the global materials first so that meshes referring to them can find them later - ConvertMaterials( pScene, pData->mGlobalMaterials); + ConvertMaterials(pScene, pData->mGlobalMaterials); // copy nodes, extracting meshes and materials on the way - pScene->mRootNode = CreateNodes( pScene, nullptr, pData->mRootNode); + pScene->mRootNode = CreateNodes(pScene, nullptr, pData->mRootNode); // extract animations - CreateAnimations( pScene, pData); + CreateAnimations(pScene, pData); // read the global meshes that were stored outside of any node - if( !pData->mGlobalMeshes.empty() ) { + if (!pData->mGlobalMeshes.empty()) { // create a root node to hold them if there isn't any, yet - if( pScene->mRootNode == nullptr ) { + if (pScene->mRootNode == nullptr) { pScene->mRootNode = new aiNode; - pScene->mRootNode->mName.Set( "$dummy_node"); + pScene->mRootNode->mName.Set("$dummy_node"); } // convert all global meshes and store them in the root node. // If there was one before, the global meshes now suddenly have its transformation matrix... // Don't know what to do there, I don't want to insert another node under the present root node // just to avoid this. - CreateMeshes( pScene, pScene->mRootNode, pData->mGlobalMeshes); + CreateMeshes(pScene, pScene->mRootNode, pData->mGlobalMeshes); } if (!pScene->mRootNode) { - throw DeadlyImportError( "No root node" ); + throw DeadlyImportError("No root node"); } // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly MakeLeftHandedProcess convertProcess; - convertProcess.Execute( pScene); + convertProcess.Execute(pScene); FlipWindingOrderProcess flipper; flipper.Execute(pScene); // finally: create a dummy material if not material was imported - if( pScene->mNumMaterials == 0) { + if (pScene->mNumMaterials == 0) { pScene->mNumMaterials = 1; // create the Material - aiMaterial* mat = new aiMaterial; - int shadeMode = (int) aiShadingMode_Gouraud; - mat->AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + aiMaterial *mat = new aiMaterial; + int shadeMode = (int)aiShadingMode_Gouraud; + mat->AddProperty(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); // material colours int specExp = 1; - aiColor3D clr = aiColor3D( 0, 0, 0); - mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_EMISSIVE); - mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_SPECULAR); + aiColor3D clr = aiColor3D(0, 0, 0); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); - clr = aiColor3D( 0.5f, 0.5f, 0.5f); - mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_DIFFUSE); - mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); + clr = aiColor3D(0.5f, 0.5f, 0.5f); + mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&specExp, 1, AI_MATKEY_SHININESS); - pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials = new aiMaterial *[1]; pScene->mMaterials[0] = mat; } } // ------------------------------------------------------------------------------------------------ // Recursively creates scene nodes from the imported hierarchy. -aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFile::Node* pNode) { - if ( !pNode ) { +aiNode *XFileImporter::CreateNodes(aiScene *pScene, aiNode *pParent, const XFile::Node *pNode) { + if (!pNode) { return nullptr; } // create node - aiNode* node = new aiNode; + aiNode *node = new aiNode; node->mName.length = (ai_uint32)pNode->mName.length(); node->mParent = pParent; - memcpy( node->mName.data, pNode->mName.c_str(), pNode->mName.length()); + memcpy(node->mName.data, pNode->mName.c_str(), pNode->mName.length()); node->mName.data[node->mName.length] = 0; node->mTransformation = pNode->mTrafoMatrix; // convert meshes from the source node - CreateMeshes( pScene, node, pNode->mMeshes); + CreateMeshes(pScene, node, pNode->mMeshes); // handle children - if( !pNode->mChildren.empty() ) { + if (!pNode->mChildren.empty()) { node->mNumChildren = (unsigned int)pNode->mChildren.size(); - node->mChildren = new aiNode* [node->mNumChildren]; + node->mChildren = new aiNode *[node->mNumChildren]; - for ( unsigned int a = 0; a < pNode->mChildren.size(); ++a ) { - node->mChildren[ a ] = CreateNodes( pScene, node, pNode->mChildren[ a ] ); + for (unsigned int a = 0; a < pNode->mChildren.size(); ++a) { + node->mChildren[a] = CreateNodes(pScene, node, pNode->mChildren[a]); } } @@ -224,55 +213,55 @@ aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFil // ------------------------------------------------------------------------------------------------ // Creates the meshes for the given node. -void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vector& pMeshes) { +void XFileImporter::CreateMeshes(aiScene *pScene, aiNode *pNode, const std::vector &pMeshes) { if (pMeshes.empty()) { return; } // create a mesh for each mesh-material combination in the source node - std::vector meshes; - for( unsigned int a = 0; a < pMeshes.size(); ++a ) { - XFile::Mesh* sourceMesh = pMeshes[a]; - if ( nullptr == sourceMesh ) { + std::vector meshes; + for (unsigned int a = 0; a < pMeshes.size(); ++a) { + XFile::Mesh *sourceMesh = pMeshes[a]; + if (nullptr == sourceMesh) { continue; } // first convert its materials so that we can find them with their index afterwards - ConvertMaterials( pScene, sourceMesh->mMaterials); + ConvertMaterials(pScene, sourceMesh->mMaterials); - unsigned int numMaterials = std::max( (unsigned int)sourceMesh->mMaterials.size(), 1u); - for( unsigned int b = 0; b < numMaterials; ++b ) { + unsigned int numMaterials = std::max((unsigned int)sourceMesh->mMaterials.size(), 1u); + for (unsigned int b = 0; b < numMaterials; ++b) { // collect the faces belonging to this material std::vector faces; unsigned int numVertices = 0; - if( !sourceMesh->mFaceMaterials.empty() ) { + if (!sourceMesh->mFaceMaterials.empty()) { // if there is a per-face material defined, select the faces with the corresponding material - for( unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c ) { - if( sourceMesh->mFaceMaterials[c] == b) { - faces.push_back( c); + for (unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c) { + if (sourceMesh->mFaceMaterials[c] == b) { + faces.push_back(c); numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); } } } else { // if there is no per-face material, place everything into one mesh - for( unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c ) { - faces.push_back( c); + for (unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c) { + faces.push_back(c); numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); } } // no faces/vertices using this material? strange... - if ( numVertices == 0 ) { + if (numVertices == 0) { continue; } // create a submesh using this material - aiMesh* mesh = new aiMesh; - meshes.push_back( mesh); + aiMesh *mesh = new aiMesh; + meshes.push_back(mesh); // find the material in the scene's material list. Either own material // or referenced material, it should already have a valid index - if( !sourceMesh->mFaceMaterials.empty() ) { + if (!sourceMesh->mFaceMaterials.empty()) { mesh->mMaterialIndex = static_cast(sourceMesh->mMaterials[b].sceneIndex); } else { mesh->mMaterialIndex = 0; @@ -289,41 +278,41 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec mesh->mName.Set(sourceMesh->mName); // normals? - if ( sourceMesh->mNormals.size() > 0 ) { - mesh->mNormals = new aiVector3D[ numVertices ]; + if (sourceMesh->mNormals.size() > 0) { + mesh->mNormals = new aiVector3D[numVertices]; } // texture coords - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) { - if ( !sourceMesh->mTexCoords[ c ].empty() ) { - mesh->mTextureCoords[ c ] = new aiVector3D[ numVertices ]; + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { + if (!sourceMesh->mTexCoords[c].empty()) { + mesh->mTextureCoords[c] = new aiVector3D[numVertices]; } } // vertex colors - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) { - if ( !sourceMesh->mColors[ c ].empty() ) { - mesh->mColors[ c ] = new aiColor4D[ numVertices ]; + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { + if (!sourceMesh->mColors[c].empty()) { + mesh->mColors[c] = new aiColor4D[numVertices]; } } // now collect the vertex data of all data streams present in the imported mesh - unsigned int newIndex( 0 ); + unsigned int newIndex(0); std::vector orgPoints; // from which original point each new vertex stems - orgPoints.resize( numVertices, 0); + orgPoints.resize(numVertices, 0); - for( unsigned int c = 0; c < faces.size(); ++c ) { + for (unsigned int c = 0; c < faces.size(); ++c) { unsigned int f = faces[c]; // index of the source face - const XFile::Face& pf = sourceMesh->mPosFaces[f]; // position source face + const XFile::Face &pf = sourceMesh->mPosFaces[f]; // position source face // create face. either triangle or triangle fan depending on the index count - aiFace& df = mesh->mFaces[c]; // destination face + aiFace &df = mesh->mFaces[c]; // destination face df.mNumIndices = (unsigned int)pf.mIndices.size(); - df.mIndices = new unsigned int[ df.mNumIndices]; + df.mIndices = new unsigned int[df.mNumIndices]; // collect vertex data for indices of this face - for( unsigned int d = 0; d < df.mNumIndices; ++d ) { - df.mIndices[ d ] = newIndex; - const unsigned int newIdx( pf.mIndices[ d ] ); - if ( newIdx > sourceMesh->mPositions.size() ) { + for (unsigned int d = 0; d < df.mNumIndices; ++d) { + df.mIndices[d] = newIndex; + const unsigned int newIdx = pf.mIndices[d]; + if (newIdx >= sourceMesh->mPositions.size()) { continue; } @@ -332,24 +321,26 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec // Position mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]]; // Normal, if present - if ( mesh->HasNormals() ) { - if ( sourceMesh->mNormFaces[ f ].mIndices.size() > d ) { - const size_t idx( sourceMesh->mNormFaces[ f ].mIndices[ d ] ); - mesh->mNormals[ newIndex ] = sourceMesh->mNormals[ idx ]; + if (mesh->HasNormals()) { + if (sourceMesh->mNormFaces[f].mIndices.size() > d) { + const size_t idx(sourceMesh->mNormFaces[f].mIndices[d]); + if (idx < sourceMesh->mNormals.size()) { + mesh->mNormals[newIndex] = sourceMesh->mNormals[idx]; + } } } // texture coord sets - for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e ) { - if( mesh->HasTextureCoords( e)) { + for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e) { + if (mesh->HasTextureCoords(e)) { aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]]; - mesh->mTextureCoords[e][newIndex] = aiVector3D( tex.x, 1.0f - tex.y, 0.0f); + mesh->mTextureCoords[e][newIndex] = aiVector3D(tex.x, 1.0f - tex.y, 0.0f); } } // vertex color sets - for ( unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e ) { - if ( mesh->HasVertexColors( e ) ) { - mesh->mColors[ e ][ newIndex ] = sourceMesh->mColors[ e ][ pf.mIndices[ d ] ]; + for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e) { + if (mesh->HasVertexColors(e)) { + mesh->mColors[e][newIndex] = sourceMesh->mColors[e][pf.mIndices[d]]; } } @@ -358,63 +349,74 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec } // there should be as much new vertices as we calculated before - ai_assert( newIndex == numVertices); + ai_assert(newIndex == numVertices); // convert all bones of the source mesh which influence vertices in this newly created mesh - const std::vector& bones = sourceMesh->mBones; - std::vector newBones; - for( unsigned int c = 0; c < bones.size(); ++c ) { - const XFile::Bone& obone = bones[c]; + const std::vector &bones = sourceMesh->mBones; + std::vector newBones; + for (unsigned int c = 0; c < bones.size(); ++c) { + const XFile::Bone &obone = bones[c]; // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex - std::vector oldWeights( sourceMesh->mPositions.size(), 0.0); - for ( unsigned int d = 0; d < obone.mWeights.size(); ++d ) { - oldWeights[ obone.mWeights[ d ].mVertex ] = obone.mWeights[ d ].mWeight; + std::vector oldWeights(sourceMesh->mPositions.size(), 0.0); + for (unsigned int d = 0; d < obone.mWeights.size(); ++d) { + // TODO The conditional against boneIdx which was added in commit f844c33 + // TODO (https://github.com/assimp/assimp/commit/f844c3397d7726477ab0fdca8efd3df56c18366b) + // TODO causes massive breakage as detailed in: + // TODO https://github.com/assimp/assimp/issues/5332 + // TODO In cases like this unit tests are less useful, since the model still has + // TODO meshes, textures, animations etc. and asserts against these values may pass; + // TODO when touching importer code, it is crucial that developers also run manual, visual + // TODO checks to ensure there's no obvious breakage _before_ commiting to main branch + //const unsigned int boneIdx = obone.mWeights[d].mVertex; + //if (boneIdx < obone.mWeights.size()) { + oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight; + //} } // collect all vertex weights that influence a vertex in the new mesh std::vector newWeights; - newWeights.reserve( numVertices); - for( unsigned int d = 0; d < orgPoints.size(); ++d ) { + newWeights.reserve(numVertices); + for (unsigned int d = 0; d < orgPoints.size(); ++d) { // does the new vertex stem from an old vertex which was influenced by this bone? ai_real w = oldWeights[orgPoints[d]]; - if ( w > 0.0 ) { - newWeights.emplace_back( d, w ); + if (w > 0.0) { + newWeights.emplace_back(d, w); } } // if the bone has no weights in the newly created mesh, ignore it - if ( newWeights.empty() ) { + if (newWeights.empty()) { continue; } // create - aiBone* nbone = new aiBone; - newBones.push_back( nbone); + aiBone *nbone = new aiBone; + newBones.push_back(nbone); // copy name and matrix - nbone->mName.Set( obone.mName); + nbone->mName.Set(obone.mName); nbone->mOffsetMatrix = obone.mOffsetMatrix; nbone->mNumWeights = (unsigned int)newWeights.size(); nbone->mWeights = new aiVertexWeight[nbone->mNumWeights]; - for ( unsigned int d = 0; d < newWeights.size(); ++d ) { - nbone->mWeights[ d ] = newWeights[ d ]; + for (unsigned int d = 0; d < newWeights.size(); ++d) { + nbone->mWeights[d] = newWeights[d]; } } // store the bones in the mesh mesh->mNumBones = (unsigned int)newBones.size(); - if( !newBones.empty()) { - mesh->mBones = new aiBone*[mesh->mNumBones]; - std::copy( newBones.begin(), newBones.end(), mesh->mBones); + if (!newBones.empty()) { + mesh->mBones = new aiBone *[mesh->mNumBones]; + std::copy(newBones.begin(), newBones.end(), mesh->mBones); } } } // reallocate scene mesh array to be large enough - aiMesh** prevArray = pScene->mMeshes; - pScene->mMeshes = new aiMesh*[pScene->mNumMeshes + meshes.size()]; - if( prevArray) { - memcpy( pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof( aiMesh*)); - delete [] prevArray; + aiMesh **prevArray = pScene->mMeshes; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes + meshes.size()]; + if (prevArray) { + memcpy(pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof(aiMesh *)); + delete[] prevArray; } // allocate mesh index array in the node @@ -422,7 +424,7 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; // store all meshes in the mesh library of the scene and store their indices in the node - for( unsigned int a = 0; a < meshes.size(); a++) { + for (unsigned int a = 0; a < meshes.size(); a++) { pScene->mMeshes[pScene->mNumMeshes] = meshes[a]; pNode->mMeshes[a] = pScene->mNumMeshes; pScene->mNumMeshes++; @@ -431,35 +433,34 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec // ------------------------------------------------------------------------------------------------ // Converts the animations from the given imported data and creates them in the scene. -void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData) { - std::vector newAnims; +void XFileImporter::CreateAnimations(aiScene *pScene, const XFile::Scene *pData) { + std::vector newAnims; - for( unsigned int a = 0; a < pData->mAnims.size(); ++a ) { - const XFile::Animation* anim = pData->mAnims[a]; + for (unsigned int a = 0; a < pData->mAnims.size(); ++a) { + const XFile::Animation *anim = pData->mAnims[a]; // some exporters mock me with empty animation tags. - if ( anim->mAnims.empty() ) { + if (anim->mAnims.empty()) { continue; } // create a new animation to hold the data - aiAnimation* nanim = new aiAnimation; - newAnims.push_back( nanim); - nanim->mName.Set( anim->mName); + aiAnimation *nanim = new aiAnimation; + newAnims.push_back(nanim); + nanim->mName.Set(anim->mName); // duration will be determined by the maximum length nanim->mDuration = 0; nanim->mTicksPerSecond = pData->mAnimTicksPerSecond; nanim->mNumChannels = (unsigned int)anim->mAnims.size(); - nanim->mChannels = new aiNodeAnim*[nanim->mNumChannels]; + nanim->mChannels = new aiNodeAnim *[nanim->mNumChannels]; - for( unsigned int b = 0; b < anim->mAnims.size(); ++b ) { - const XFile::AnimBone* bone = anim->mAnims[b]; - aiNodeAnim* nbone = new aiNodeAnim; - nbone->mNodeName.Set( bone->mBoneName); + for (unsigned int b = 0; b < anim->mAnims.size(); ++b) { + const XFile::AnimBone *bone = anim->mAnims[b]; + aiNodeAnim *nbone = new aiNodeAnim; + nbone->mNodeName.Set(bone->mBoneName); nanim->mChannels[b] = nbone; // key-frames are given as combined transformation matrix keys - if( !bone->mTrafoKeys.empty() ) - { + if (!bone->mTrafoKeys.empty()) { nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size(); nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size(); @@ -467,44 +468,44 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size(); nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; - for( unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) { + for (unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) { // deconstruct each matrix into separate position, rotation and scaling double time = bone->mTrafoKeys[c].mTime; aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix; // extract position - aiVector3D pos( trafo.a4, trafo.b4, trafo.c4); + aiVector3D pos(trafo.a4, trafo.b4, trafo.c4); nbone->mPositionKeys[c].mTime = time; nbone->mPositionKeys[c].mValue = pos; // extract scaling aiVector3D scale; - scale.x = aiVector3D( trafo.a1, trafo.b1, trafo.c1).Length(); - scale.y = aiVector3D( trafo.a2, trafo.b2, trafo.c2).Length(); - scale.z = aiVector3D( trafo.a3, trafo.b3, trafo.c3).Length(); + scale.x = aiVector3D(trafo.a1, trafo.b1, trafo.c1).Length(); + scale.y = aiVector3D(trafo.a2, trafo.b2, trafo.c2).Length(); + scale.z = aiVector3D(trafo.a3, trafo.b3, trafo.c3).Length(); nbone->mScalingKeys[c].mTime = time; nbone->mScalingKeys[c].mValue = scale; // reconstruct rotation matrix without scaling aiMatrix3x3 rotmat( - trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z, - trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z, - trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z); + trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z, + trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z, + trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z); // and convert it into a quaternion nbone->mRotationKeys[c].mTime = time; - nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat); } // longest lasting key sequence determines duration - nanim->mDuration = std::max( nanim->mDuration, bone->mTrafoKeys.back().mTime); + nanim->mDuration = std::max(nanim->mDuration, bone->mTrafoKeys.back().mTime); } else { // separate key sequences for position, rotation, scaling nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size(); if (nbone->mNumPositionKeys != 0) { nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; - for( unsigned int c = 0; c < nbone->mNumPositionKeys; ++c ) { + for (unsigned int c = 0; c < nbone->mNumPositionKeys; ++c) { aiVector3D pos = bone->mPosKeys[c].mValue; nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime; @@ -516,11 +517,11 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size(); if (nbone->mNumRotationKeys != 0) { nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; - for( unsigned int c = 0; c < nbone->mNumRotationKeys; ++c ) { + for (unsigned int c = 0; c < nbone->mNumRotationKeys; ++c) { aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix(); nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime; - nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat); nbone->mRotationKeys[c].mValue.w *= -1.0f; // needs quat inversion } } @@ -529,153 +530,149 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData nbone->mNumScalingKeys = (unsigned int)bone->mScaleKeys.size(); if (nbone->mNumScalingKeys != 0) { nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; - for( unsigned int c = 0; c < nbone->mNumScalingKeys; c++) + for (unsigned int c = 0; c < nbone->mNumScalingKeys; c++) nbone->mScalingKeys[c] = bone->mScaleKeys[c]; } // longest lasting key sequence determines duration - if( bone->mPosKeys.size() > 0) - nanim->mDuration = std::max( nanim->mDuration, bone->mPosKeys.back().mTime); - if( bone->mRotKeys.size() > 0) - nanim->mDuration = std::max( nanim->mDuration, bone->mRotKeys.back().mTime); - if( bone->mScaleKeys.size() > 0) - nanim->mDuration = std::max( nanim->mDuration, bone->mScaleKeys.back().mTime); + if (bone->mPosKeys.size() > 0) + nanim->mDuration = std::max(nanim->mDuration, bone->mPosKeys.back().mTime); + if (bone->mRotKeys.size() > 0) + nanim->mDuration = std::max(nanim->mDuration, bone->mRotKeys.back().mTime); + if (bone->mScaleKeys.size() > 0) + nanim->mDuration = std::max(nanim->mDuration, bone->mScaleKeys.back().mTime); } } } // store all converted animations in the scene - if( newAnims.size() > 0) - { + if (newAnims.size() > 0) { pScene->mNumAnimations = (unsigned int)newAnims.size(); - pScene->mAnimations = new aiAnimation* [pScene->mNumAnimations]; - for( unsigned int a = 0; a < newAnims.size(); a++) + pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations]; + for (unsigned int a = 0; a < newAnims.size(); a++) pScene->mAnimations[a] = newAnims[a]; } } // ------------------------------------------------------------------------------------------------ // Converts all materials in the given array and stores them in the scene's material list. -void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector& pMaterials) -{ +void XFileImporter::ConvertMaterials(aiScene *pScene, std::vector &pMaterials) { // count the non-referrer materials in the array - unsigned int numNewMaterials( 0 ); - for ( unsigned int a = 0; a < pMaterials.size(); ++a ) { - if ( !pMaterials[ a ].mIsReference ) { + unsigned int numNewMaterials(0); + for (unsigned int a = 0; a < pMaterials.size(); ++a) { + if (!pMaterials[a].mIsReference) { ++numNewMaterials; } } // resize the scene's material list to offer enough space for the new materials - if( numNewMaterials > 0 ) { - aiMaterial** prevMats = pScene->mMaterials; - pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials + numNewMaterials]; - if( nullptr != prevMats) { - ::memcpy( pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof( aiMaterial*)); - delete [] prevMats; + if (numNewMaterials > 0) { + aiMaterial **prevMats = pScene->mMaterials; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials + numNewMaterials]; + if (nullptr != prevMats) { + ::memcpy(pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof(aiMaterial *)); + delete[] prevMats; } } // convert all the materials given in the array - for( unsigned int a = 0; a < pMaterials.size(); ++a ) { - XFile::Material& oldMat = pMaterials[a]; - if( oldMat.mIsReference) { + for (unsigned int a = 0; a < pMaterials.size(); ++a) { + XFile::Material &oldMat = pMaterials[a]; + if (oldMat.mIsReference) { // find the material it refers to by name, and store its index - for( size_t b = 0; b < pScene->mNumMaterials; ++b ) { + for (size_t b = 0; b < pScene->mNumMaterials; ++b) { aiString name; - pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name); - if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) { - oldMat.sceneIndex = a; + pScene->mMaterials[b]->Get(AI_MATKEY_NAME, name); + if (strcmp(name.C_Str(), oldMat.mName.data()) == 0) { + oldMat.sceneIndex = b; break; } } - if( oldMat.sceneIndex == SIZE_MAX ) { - ASSIMP_LOG_WARN( "Could not resolve global material reference \"", oldMat.mName, "\"" ); + if (oldMat.sceneIndex == SIZE_MAX) { + ASSIMP_LOG_WARN("Could not resolve global material reference \"", oldMat.mName, "\""); oldMat.sceneIndex = 0; } continue; } - aiMaterial* mat = new aiMaterial; + aiMaterial *mat = new aiMaterial; aiString name; - name.Set( oldMat.mName); - mat->AddProperty( &name, AI_MATKEY_NAME); + name.Set(oldMat.mName); + mat->AddProperty(&name, AI_MATKEY_NAME); // Shading model: hard-coded to PHONG, there is no such information in an XFile // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix // for some models in the SDK (e.g. good old tiny.x) - int shadeMode = (int)oldMat.mSpecularExponent == 0.0f - ? aiShadingMode_Gouraud : aiShadingMode_Phong; + int shadeMode = (int)oldMat.mSpecularExponent == 0.0f ? aiShadingMode_Gouraud : aiShadingMode_Phong; - mat->AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + mat->AddProperty(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); // material colours // Unclear: there's no ambient colour, but emissive. What to put for ambient? // Probably nothing at all, let the user select a suitable default. - mat->AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); - mat->AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); - mat->AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); - mat->AddProperty( &oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); - + mat->AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); // texture, if there is one - if (1 == oldMat.mTextures.size() ) { - const XFile::TexEntry& otex = oldMat.mTextures.back(); + if (1 == oldMat.mTextures.size()) { + const XFile::TexEntry &otex = oldMat.mTextures.back(); if (otex.mName.length()) { // if there is only one texture assume it contains the diffuse color - aiString tex( otex.mName); - if ( otex.mIsNormalMap ) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS( 0 ) ); + aiString tex(otex.mName); + if (otex.mIsNormalMap) { + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(0)); } else { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE( 0 ) ); + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); } } } else { // Otherwise ... try to search for typical strings in the // texture's file name like 'bump' or 'diffuse' - unsigned int iHM = 0,iNM = 0,iDM = 0,iSM = 0,iAM = 0,iEM = 0; - for( unsigned int b = 0; b < oldMat.mTextures.size(); ++b ) { - const XFile::TexEntry& otex = oldMat.mTextures[b]; + unsigned int iHM = 0, iNM = 0, iDM = 0, iSM = 0, iAM = 0, iEM = 0; + for (unsigned int b = 0; b < oldMat.mTextures.size(); ++b) { + const XFile::TexEntry &otex = oldMat.mTextures[b]; std::string sz = otex.mName; - if ( !sz.length() ) { + if (!sz.length()) { continue; } // find the file name std::string::size_type s = sz.find_last_of("\\/"); - if ( std::string::npos == s ) { + if (std::string::npos == s) { s = 0; } // cut off the file extension std::string::size_type sExt = sz.find_last_of('.'); - if (std::string::npos != sExt){ + if (std::string::npos != sExt) { sz[sExt] = '\0'; } // convert to lower case for easier comparison - for ( unsigned int c = 0; c < sz.length(); ++c ) { - sz[ c ] = (char) tolower( (unsigned char) sz[ c ] ); + for (unsigned int c = 0; c < sz.length(); ++c) { + sz[c] = (char)tolower((unsigned char)sz[c]); } // Place texture filename property under the corresponding name - aiString tex( oldMat.mTextures[b].mName); + aiString tex(oldMat.mTextures[b].mName); // bump map if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++)); - } else if (otex.mIsNormalMap || std::string::npos != sz.find( "normal", s) || std::string::npos != sz.find("nm", s)) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS(iNM++)); - } else if (std::string::npos != sz.find( "spec", s) || std::string::npos != sz.find( "glanz", s)) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++)); - } else if (std::string::npos != sz.find( "ambi", s) || std::string::npos != sz.find( "env", s)) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++)); - } else if (std::string::npos != sz.find( "emissive", s) || std::string::npos != sz.find( "self", s)) { - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++)); + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++)); + } else if (otex.mIsNormalMap || std::string::npos != sz.find("normal", s) || std::string::npos != sz.find("nm", s)) { + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(iNM++)); + } else if (std::string::npos != sz.find("spec", s) || std::string::npos != sz.find("glanz", s)) { + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++)); + } else if (std::string::npos != sz.find("ambi", s) || std::string::npos != sz.find("env", s)) { + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++)); + } else if (std::string::npos != sz.find("emissive", s) || std::string::npos != sz.find("self", s)) { + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++)); } else { // Assume it is a diffuse texture - mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++)); + mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++)); } } } @@ -686,4 +683,6 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector &pBuffer) : P1 += ofs; est_out += MSZIP_BLOCK; // one decompressed block is 327861 in size } - + // Allocate storage and terminating zero and do the actual uncompressing Compression compression; uncompressed.resize(est_out + 1); @@ -839,7 +839,6 @@ void XFileParser::ParseDataObjectAnimationKey(AnimBone *pAnimBone) { default: ThrowException("Unknown key type ", keyType, " in animation."); - break; } // end switch // key separator diff --git a/Engine/lib/assimp/code/AssetLib/X/XFileParser.h b/Engine/lib/assimp/code/AssetLib/X/XFileParser.h index 36eac2ada..32375511a 100644 --- a/Engine/lib/assimp/code/AssetLib/X/XFileParser.h +++ b/Engine/lib/assimp/code/AssetLib/X/XFileParser.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DExporter.hpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DExporter.hpp index 7a87821b4..babf552dd 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DExporter.hpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DExporter.hpp @@ -52,22 +52,12 @@ class X3DExporter { struct SAttribute { const std::string Name; const std::string Value; - SAttribute() : - Name(), - Value() { - // empty - } + SAttribute() = default; SAttribute(const std::string &name, const std::string &value) : Name(name), Value(value) { // empty } - - SAttribute(SAttribute &&rhs) AI_NO_EXCEPT : - Name(rhs.Name), - Value(rhs.Value) { - // empty - } }; /***********************************************/ diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp index e89aeb428..1c962a460 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.cpp @@ -188,12 +188,57 @@ mg_m_err: pFaces.clear(); } +void X3DGeoHelper::coordIdx_str2lines_arr(const std::vector &pCoordIdx, std::vector &pFaces) { + std::vector f_data(pCoordIdx); + + if (f_data.back() != (-1)) { + f_data.push_back(-1); + } + + // reserve average size. + pFaces.reserve(f_data.size() / 2); + for (std::vector::const_iterator startIt = f_data.cbegin(), endIt = f_data.cbegin(); endIt != f_data.cend(); ++endIt) { + // check for end of current polyline + if (*endIt != -1) + continue; + + // found end of polyline, check if this is a valid polyline + std::size_t numIndices = std::distance(startIt, endIt); + if (numIndices <= 1) + goto mg_m_err; + + // create line faces out of polyline indices + for (int32_t idx0 = *startIt++; startIt != endIt; ++startIt) { + int32_t idx1 = *startIt; + + aiFace tface; + tface.mNumIndices = 2; + tface.mIndices = new unsigned int[2]; + tface.mIndices[0] = idx0; + tface.mIndices[1] = idx1; + pFaces.push_back(tface); + + idx0 = idx1; + } + + ++startIt; + } + + return; + +mg_m_err: + for (size_t i = 0, i_e = pFaces.size(); i < i_e; i++) + delete[] pFaces[i].mIndices; + + pFaces.clear(); +} + void X3DGeoHelper::add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex) { std::list tcol; // create RGBA array from RGB. for (std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it) - tcol.emplace_back((*it).r, (*it).g, (*it).b, 1); + tcol.emplace_back((*it).r, (*it).g, (*it).b, static_cast(1)); // call existing function for adding RGBA colors add_color(pMesh, tcol, pColorPerVertex); @@ -238,7 +283,7 @@ void X3DGeoHelper::add_color(aiMesh &pMesh, const std::vector &pCoordId // create RGBA array from RGB. for (std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it) { - tcol.emplace_back((*it).r, (*it).g, (*it).b, 1); + tcol.emplace_back((*it).r, (*it).g, (*it).b, static_cast(1)); } // call existing function for adding RGBA colors @@ -440,7 +485,7 @@ void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::vector &pCoo // copy list to array because we are need indexed access to normals. texcoord_arr_copy.reserve(pTexCoords.size()); for (std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { - texcoord_arr_copy.emplace_back((*it).x, (*it).y, 0); + texcoord_arr_copy.emplace_back((*it).x, (*it).y, static_cast(0)); } if (pTexCoordIdx.size() > 0) { @@ -480,7 +525,7 @@ void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::list &pTe // copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus. tc_arr_copy.reserve(pTexCoords.size()); for (std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { - tc_arr_copy.emplace_back((*it).x, (*it).y, 0); + tc_arr_copy.emplace_back((*it).x, (*it).y, static_cast(0)); } // copy texture coordinates to mesh @@ -528,4 +573,40 @@ aiMesh *X3DGeoHelper::make_mesh(const std::vector &pCoordIdx, const std return tmesh; } +aiMesh *X3DGeoHelper::make_line_mesh(const std::vector &pCoordIdx, const std::list &pVertices) { + std::vector faces; + + // create faces array from input string with vertices indices. + X3DGeoHelper::coordIdx_str2lines_arr(pCoordIdx, faces); + if (!faces.size()) { + throw DeadlyImportError("Failed to create mesh, faces list is empty."); + } + + // + // Create new mesh and copy geometry data. + // + aiMesh *tmesh = new aiMesh; + size_t ts = faces.size(); + // faces + tmesh->mFaces = new aiFace[ts]; + tmesh->mNumFaces = static_cast(ts); + for (size_t i = 0; i < ts; i++) + tmesh->mFaces[i] = faces[i]; + + // vertices + std::list::const_iterator vit = pVertices.begin(); + + ts = pVertices.size(); + tmesh->mVertices = new aiVector3D[ts]; + tmesh->mNumVertices = static_cast(ts); + for (size_t i = 0; i < ts; i++) { + tmesh->mVertices[i] = *vit++; + } + + // set primitive type and return result. + tmesh->mPrimitiveTypes = aiPrimitiveType_LINE; + + return tmesh; +} + } // namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.h b/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.h index 78e57f9da..c740b4288 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.h +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DGeoHelper.h @@ -21,6 +21,7 @@ public: static void polylineIdx_to_lineIdx(const std::list &pPolylineCoordIdx, std::list &pLineCoordIdx); static void rect_parallel_epiped(const aiVector3D &pSize, std::list &pVertices); static void coordIdx_str2faces_arr(const std::vector &pCoordIdx, std::vector &pFaces, unsigned int &pPrimitiveTypes); + static void coordIdx_str2lines_arr(const std::vector &pCoordIdx, std::vector &pFaces); static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); static void add_color(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pColorIdx, @@ -34,6 +35,7 @@ public: const std::list &pTexCoords); static void add_tex_coord(aiMesh &pMesh, const std::list &pTexCoords); static aiMesh *make_mesh(const std::vector &pCoordIdx, const std::list &pVertices); + static aiMesh *make_line_mesh(const std::vector &pCoordIdx, const std::list &pVertices); }; } // namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.cpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.cpp index e28c9e5f3..ada388080 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -207,7 +207,7 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) { static const std::string mode = "rb"; std::unique_ptr fileStream(pIOHandler->Open(file, mode)); - if (!fileStream.get()) { + if (!fileStream) { throw DeadlyImportError("Failed to open file " + file + "."); } @@ -471,7 +471,7 @@ void X3DImporter::ParseHelper_Node_Enter(X3DNodeElementBase *pNode) { mNodeElementCur->Children.push_back(pNode); // add new element to current element child list. mNodeElementCur = pNode; // switch current element to new one. -} +} void X3DImporter::ParseHelper_Node_Exit() { // check if we can walk up. diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.hpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.hpp index 8852b71ec..623160a38 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.hpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter.hpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,6 +55,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include namespace Assimp { +AI_WONT_RETURN inline void Throw_ArgOutOfRange(const std::string &argument) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_CloseNotFound(const std::string &node) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrF(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrD(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrB(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrI(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_DEF_And_USE(const std::string &nodeName) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_TagCountIncorrect(const std::string &pNode) AI_WONT_RETURN_SUFFIX; +AI_WONT_RETURN inline void Throw_USE_NotFound(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX; inline void Throw_ArgOutOfRange(const std::string &argument) { throw DeadlyImportError("Argument value is out of range for: \"" + argument + "\"."); diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp index 653203b4e..b4562ace2 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -151,7 +151,7 @@ void X3DImporter::readArcClose2D(XmlNode &node) { std::list &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. if ((closureType == "PIE") || (closureType == "\"PIE\"")) - vlist.emplace_back(0, 0, 0); // center point - first radial line + vlist.emplace_back(static_cast(0), static_cast(0), static_cast(0)); // center point - first radial line else if ((closureType != "CHORD") && (closureType != "\"CHORD\"")) Throw_IncorrectAttrValue("ArcClose2D", "closureType"); @@ -263,7 +263,7 @@ void X3DImporter::readDisk2D(XmlNode &node) { // if (tlist_i.size() < 2) { // tlist_i and tlist_o has equal size. - throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); + throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); } // add all quads except last @@ -323,7 +323,7 @@ void X3DImporter::readPolyline2D(XmlNode &node) { // convert vec2 to vec3 for (std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) - tlist.emplace_back(it2->x, it2->y, 0); + tlist.emplace_back(it2->x, it2->y, static_cast(0)); // convert point set to line set X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); @@ -361,7 +361,7 @@ void X3DImporter::readPolypoint2D(XmlNode &node) { // convert vec2 to vec3 for (std::list::iterator it2 = point.begin(); it2 != point.end(); ++it2) { - ((X3DNodeElementGeometry2D *)ne)->Vertices.emplace_back(it2->x, it2->y, 0); + ((X3DNodeElementGeometry2D *)ne)->Vertices.emplace_back(it2->x, it2->y, static_cast(0)); } ((X3DNodeElementGeometry2D *)ne)->NumIndices = 1; @@ -405,10 +405,10 @@ void X3DImporter::readRectangle2D(XmlNode &node) { float y2 = size.y / 2.0f; std::list &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. - vlist.emplace_back(x2, y1, 0); // 1st point - vlist.emplace_back(x2, y2, 0); // 2nd point - vlist.emplace_back(x1, y2, 0); // 3rd point - vlist.emplace_back(x1, y1, 0); // 4th point + vlist.emplace_back(x2, y1, static_cast(0)); // 1st point + vlist.emplace_back(x2, y2, static_cast(0)); // 2nd point + vlist.emplace_back(x1, y2, static_cast(0)); // 3rd point + vlist.emplace_back(x1, y1, static_cast(0)); // 4th point ((X3DNodeElementGeometry2D *)ne)->Solid = solid; ((X3DNodeElementGeometry2D *)ne)->NumIndices = 4; // check for X3DMetadataObject childs. @@ -449,7 +449,7 @@ void X3DImporter::readTriangleSet2D(XmlNode &node) { // convert vec2 to vec3 for (std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { - ((X3DNodeElementGeometry2D *)ne)->Vertices.emplace_back(it2->x, it2->y, 0); + ((X3DNodeElementGeometry2D *)ne)->Vertices.emplace_back(it2->x, it2->y, static_cast(0)); } ((X3DNodeElementGeometry2D *)ne)->Solid = solid; diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp index 8d33c4b7a..62bf857e4 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Node.hpp @@ -108,9 +108,7 @@ struct X3DNodeElementBase { std::list Children; X3DElemType Type; - virtual ~X3DNodeElementBase() { - // empty - } + virtual ~X3DNodeElementBase() = default; protected: X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : @@ -367,9 +365,7 @@ struct X3DNodeElementMeta : X3DNodeElementBase { std::string Name; ///< Name of metadata object. std::string Reference; - virtual ~X3DNodeElementMeta() { - // empty - } + virtual ~X3DNodeElementMeta() = default; protected: X3DNodeElementMeta(X3DElemType type, X3DNodeElementBase *parent) : diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp index 87121ef5f..216929076 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DImporter_Postprocess.cpp @@ -320,7 +320,7 @@ void X3DImporter::Postprocess_BuildMesh(const X3DNodeElementBase &pNodeElement, // at first search for node and create mesh. for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { - *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + *pMesh = X3DGeoHelper::make_line_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); } } diff --git a/Engine/lib/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp b/Engine/lib/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp index ff24b74b3..7ed2e8237 100644 --- a/Engine/lib/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp +++ b/Engine/lib/assimp/code/AssetLib/X3D/X3DXmlHelper.cpp @@ -12,7 +12,6 @@ bool X3DXmlHelper::getColor3DAttribute(XmlNode &node, const char *attributeName, tokenize(val, values, " "); if (values.size() != 3) { Throw_ConvertFail_Str2ArrF(node.name(), attributeName); - return false; } auto it = values.begin(); color.r = stof(*it++); @@ -30,7 +29,6 @@ bool X3DXmlHelper::getVector2DAttribute(XmlNode &node, const char *attributeName tokenize(val, values, " "); if (values.size() != 2) { Throw_ConvertFail_Str2ArrF(node.name(), attributeName); - return false; } auto it = values.begin(); color.x = stof(*it++); @@ -47,7 +45,6 @@ bool X3DXmlHelper::getVector3DAttribute(XmlNode &node, const char *attributeName tokenize(val, values, " "); if (values.size() != 3) { Throw_ConvertFail_Str2ArrF(node.name(), attributeName); - return false; } auto it = values.begin(); color.x = stof(*it++); diff --git a/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.cpp b/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.cpp index 154af9854..614fac641 100644 --- a/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.cpp +++ b/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,65 +53,49 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -//#include -//#include -using namespace Assimp; +#include +#include -namespace Assimp { // this has to be in here because LogFunctions is in ::Assimp +namespace Assimp { + +static constexpr uint32_t ErrorId = ~0u; template <> const char *LogFunctions::Prefix() { - static auto prefix = "XGL: "; - return prefix; + return "XGL: "; } -} // namespace Assimp - -static const aiImporterDesc desc = { - "XGL Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, - 0, - 0, - 0, - 0, - "xgl zgl" -}; +static constexpr aiImporterDesc desc = { + "XGL Importer", "", "", "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, 0, 0, 0, "xgl zgl"}; // ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -XGLImporter::XGLImporter() : - mXmlParser(nullptr), - m_scene(nullptr) { +XGLImporter::XGLImporter() : mXmlParser(nullptr), m_scene(nullptr) { // empty } // ------------------------------------------------------------------------------------------------ -// Destructor, private as well XGLImporter::~XGLImporter() { - delete mXmlParser; + clear(); } // ------------------------------------------------------------------------------------------------ -// Returns whether the class can handle the format of the given file. bool XGLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { static const char *tokens[] = { "", "", "" }; return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); } // ------------------------------------------------------------------------------------------------ -// Get a list of all file extensions which are handled by this class const aiImporterDesc *XGLImporter::GetInfo() const { return &desc; } // ------------------------------------------------------------------------------------------------ -// Imports the given file into the given scene structure. void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { - #ifndef ASSIMP_BUILD_NO_COMPRESSED_XGL + clear(); +#ifndef ASSIMP_BUILD_NO_COMPRESSED_XGL std::vector uncompressed; #endif @@ -119,11 +103,11 @@ void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); // check whether we can read from the file - if (stream.get() == NULL) { - throw DeadlyImportError("Failed to open XGL/ZGL file " + pFile); - } + if (stream == nullptr) { + throw DeadlyImportError("Failed to open XGL/ZGL file " + pFile); + } - // see if its compressed, if so uncompress it + // see if its compressed, if so uncompress it if (GetExtension(pFile) == "zgl") { #ifdef ASSIMP_BUILD_NO_COMPRESSED_XGL ThrowException("Cannot read ZGL file since Assimp was built without compression support"); @@ -139,7 +123,7 @@ void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy compression.close(); } // replace the input stream with a memory stream - stream.reset(new MemoryIOStream(reinterpret_cast(uncompressed.data()), total)); + stream = std::make_shared(reinterpret_cast(uncompressed.data()), total); #endif } @@ -157,7 +141,7 @@ void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::vector &meshes = scope.meshes_linear; std::vector &materials = scope.materials_linear; - if (!meshes.size() || !materials.size()) { + if (meshes.empty() || materials.empty()) { ThrowException("failed to extract data from XGL file, no meshes loaded"); } @@ -182,6 +166,13 @@ void XGLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy scope.dismiss(); } +// ------------------------------------------------------------------------------------------------ +void XGLImporter::clear() { + delete mXmlParser; + mXmlParser = nullptr; +} + + // ------------------------------------------------------------------------------------------------ void XGLImporter::ReadWorld(XmlNode &node, TempScope &scope) { for (XmlNode ¤tNode : node.children()) { @@ -197,9 +188,10 @@ void XGLImporter::ReadWorld(XmlNode &node, TempScope &scope) { } aiNode *const nd = ReadObject(node, scope); - if (!nd) { + if (nd == nullptr) { ThrowException("failure reading "); } + if (nd->mName.length == 0) { nd->mName.Set("WORLD"); } @@ -224,7 +216,7 @@ aiLight *XGLImporter::ReadDirectionalLight(XmlNode &node) { std::unique_ptr l(new aiLight()); l->mType = aiLightSource_DIRECTIONAL; find_node_by_name_predicate predicate("directionallight"); - XmlNode child = node.find_child(predicate); + XmlNode child = node.find_child(std::move(predicate)); if (child.empty()) { return nullptr; } @@ -252,15 +244,17 @@ aiNode *XGLImporter::ReadObject(XmlNode &node, TempScope &scope) { const std::string &s = ai_stdStrToLower(child.name()); if (s == "mesh") { const size_t prev = scope.meshes_linear.size(); - bool empty; - if (ReadMesh(child, scope, empty)) { + if (ReadMesh(child, scope)) { const size_t newc = scope.meshes_linear.size(); for (size_t i = 0; i < newc - prev; ++i) { meshes.push_back(static_cast(i + prev)); } - } + } } else if (s == "mat") { - ReadMaterial(child, scope); + const uint32_t matId = ReadMaterial(child, scope); + if (matId == ErrorId) { + ThrowException("Invalid material id detected."); + } } else if (s == "object") { children.push_back(ReadObject(child, scope)); } else if (s == "objectref") { @@ -436,18 +430,25 @@ aiMesh *XGLImporter::ToOutputMesh(const TempMaterialMesh &m) { return mesh.release(); } -// ------------------------------------------------------------------------------------------------ -bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope, bool &empty) { - TempMesh t; +// ------------------------------------------------------------------------------------------------ +inline static unsigned int generateMeshId(unsigned int meshId, bool nor, bool uv) { + unsigned int currentMeshId = meshId | ((nor ? 1 : 0) << 31) | ((uv ? 1 : 0) << 30); + return currentMeshId; +} + +// ------------------------------------------------------------------------------------------------ +bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope) { + TempMesh t; + uint32_t matId = 99999; + bool mesh_created = false; std::map bymat; const unsigned int mesh_id = ReadIDAttr(node); - bool empty_mesh = true; for (XmlNode &child : node.children()) { const std::string &s = ai_stdStrToLower(child.name()); if (s == "mat") { - ReadMaterial(child, scope); + matId = ReadMaterial(child, scope); } else if (s == "p") { pugi::xml_attribute attr = child.attribute("ID"); if (attr.empty()) { @@ -475,66 +476,41 @@ bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope, bool &empty) { } else if (s == "f" || s == "l" || s == "p") { const unsigned int vcount = s == "f" ? 3 : (s == "l" ? 2 : 1); - unsigned int mid = ~0u; - TempFace tf[3]; + unsigned int meshId = ErrorId; + TempFace tempFace[3] = {}; bool has[3] = { false }; - for (XmlNode &sub_child : child.children()) { - const std::string &scn = ai_stdStrToLower(sub_child.name()); - if (scn == "fv1" || scn == "lv1" || scn == "pv1") { - ReadFaceVertex(sub_child, t, tf[0]); - has[0] = true; - } else if (scn == "fv2" || scn == "lv2") { - ReadFaceVertex(sub_child, t, tf[1]); - has[1] = true; - } else if (scn == "fv3") { - ReadFaceVertex(sub_child, t, tf[2]); - has[2] = true; - } else if (scn == "mat") { - if (mid != ~0u) { - LogWarn("only one material tag allowed per "); - } - mid = ResolveMaterialRef(sub_child, scope); - } else if (scn == "matref") { - if (mid != ~0u) { - LogWarn("only one material tag allowed per "); - } - mid = ResolveMaterialRef(sub_child, scope); - } - } - if (has[0] || has[1] || has[2]) { - empty_mesh = false; - } - - if (mid == ~0u) { + meshId = ReadVertices(child, t, tempFace, has, meshId, scope); + if (meshId == ErrorId) { ThrowException("missing material index"); } - bool nor = false; - bool uv = false; + bool nor = false, uv = false; for (unsigned int i = 0; i < vcount; ++i) { if (!has[i]) { ThrowException("missing face vertex data"); } - nor = nor || tf[i].has_normal; - uv = uv || tf[i].has_uv; + nor = nor || tempFace[i].has_normal; + uv = uv || tempFace[i].has_uv; } - if (mid >= (1 << 30)) { + if (meshId >= (1 << 30)) { LogWarn("material indices exhausted, this may cause errors in the output"); } - unsigned int meshId = mid | ((nor ? 1 : 0) << 31) | ((uv ? 1 : 0) << 30); + const unsigned int currentMeshId = generateMeshId(meshId, nor, uv); - TempMaterialMesh &mesh = bymat[meshId]; - mesh.matid = mid; + // Generate the temp mesh + TempMaterialMesh &mesh = bymat[currentMeshId]; + mesh.matid = meshId; + mesh_created = true; for (unsigned int i = 0; i < vcount; ++i) { - mesh.positions.push_back(tf[i].pos); + mesh.positions.push_back(tempFace[i].pos); if (nor) { - mesh.normals.push_back(tf[i].normal); + mesh.normals.push_back(tempFace[i].normal); } if (uv) { - mesh.uvs.push_back(tf[i].uv); + mesh.uvs.push_back(tempFace[i].uv); } mesh.pflags |= 1 << (vcount - 1); @@ -544,25 +520,59 @@ bool XGLImporter::ReadMesh(XmlNode &node, TempScope &scope, bool &empty) { } } - // finally extract output meshes and add them to the scope - using pairt = std::pair; - for (const pairt &p : bymat) { - aiMesh *const m = ToOutputMesh(p.second); - scope.meshes_linear.push_back(m); - - // if this is a definition, keep it on the stack - if (mesh_id != ~0u) { - scope.meshes.insert(std::pair(mesh_id, m)); - } - } - if (empty_mesh) { - LogWarn("Mesh is empty, skipping."); - empty = empty_mesh; - return false; + if (!mesh_created) { + TempMaterialMesh &mesh = bymat[mesh_id]; + mesh.matid = matId; } + // finally extract output meshes and add them to the scope + AppendOutputMeshes(bymat, scope, mesh_id); + // no id == not a reference, insert this mesh right *here* - return mesh_id == ~0u; + return mesh_id == ErrorId; +} + +// ---------------------------------------------------------------------------------------------- +void XGLImporter::AppendOutputMeshes(std::map bymat, TempScope &scope, + const unsigned int mesh_id) { + using pairt = std::pair; + for (const pairt &p : bymat) { + aiMesh *const m = ToOutputMesh(p.second); + scope.meshes_linear.push_back(m); + + // if this is a definition, keep it on the stack + if (mesh_id != ErrorId) { + scope.meshes.insert(std::pair(mesh_id, m)); + } + } +} + +// ---------------------------------------------------------------------------------------------- +unsigned int XGLImporter::ReadVertices(XmlNode &child, TempMesh t, TempFace *tf, bool *has, unsigned int mid, TempScope &scope) { + for (XmlNode &sub_child : child.children()) { + const std::string &scn = ai_stdStrToLower(sub_child.name()); + if (scn == "fv1" || scn == "lv1" || scn == "pv1") { + ReadFaceVertex(sub_child, t, tf[0]); + has[0] = true; + } else if (scn == "fv2" || scn == "lv2") { + ReadFaceVertex(sub_child, t, tf[1]); + has[1] = true; + } else if (scn == "fv3") { + ReadFaceVertex(sub_child, t, tf[2]); + has[2] = true; + } else if (scn == "mat") { + if (mid != ErrorId) { + LogWarn("only one material tag allowed per "); + } + mid = ResolveMaterialRef(sub_child, scope); + } else if (scn == "matref") { + if (mid != ErrorId) { + LogWarn("only one material tag allowed per "); + } + mid = ResolveMaterialRef(sub_child, scope); + } + } + return mid; } // ---------------------------------------------------------------------------------------------- @@ -596,10 +606,10 @@ unsigned int XGLImporter::ResolveMaterialRef(XmlNode &node, TempScope &scope) { } // ------------------------------------------------------------------------------------------------ -void XGLImporter::ReadMaterial(XmlNode &node, TempScope &scope) { +unsigned int XGLImporter::ReadMaterial(XmlNode &node, TempScope &scope) { const unsigned int mat_id = ReadIDAttr(node); - auto *mat(new aiMaterial); + auto *mat = new aiMaterial; for (XmlNode &child : node.children()) { const std::string &s = ai_stdStrToLower(child.name()); if (s == "amb") { @@ -625,6 +635,8 @@ void XGLImporter::ReadMaterial(XmlNode &node, TempScope &scope) { scope.materials[mat_id] = mat; scope.materials_linear.push_back(mat); + + return mat_id; } // ---------------------------------------------------------------------------------------------- @@ -681,20 +693,21 @@ unsigned int XGLImporter::ReadIDAttr(XmlNode &node) { } } - return ~0u; + return ErrorId; } // ------------------------------------------------------------------------------------------------ float XGLImporter::ReadFloat(XmlNode &node) { std::string v; XmlParser::getValueAsString(node, v); - const char *s = v.c_str(), *se; - if (!SkipSpaces(&s)) { + const char *s = v.c_str(); + const char *end = v.c_str() + v.size(); + if (!SkipSpaces(&s, end)) { LogError("unexpected EOL, failed to parse index element"); return 0.f; } - float t; - se = fast_atoreal_move(s, t); + float t{ 0.0f }; + const char *se = fast_atoreal_move(s, t); if (se == s) { LogError("failed to read float text"); return 0.f; @@ -708,16 +721,17 @@ unsigned int XGLImporter::ReadIndexFromText(XmlNode &node) { std::string v; XmlParser::getValueAsString(node, v); const char *s = v.c_str(); - if (!SkipSpaces(&s)) { + const char *end = v.c_str() + v.size(); + if (!SkipSpaces(&s, end)) { LogError("unexpected EOL, failed to parse index element"); - return ~0u; + return ErrorId; } - const char *se; + const char *se = nullptr; const unsigned int t = strtoul10(s, &se); if (se == s) { LogError("failed to read index"); - return ~0u; + return ErrorId; } return t; @@ -729,16 +743,17 @@ aiVector2D XGLImporter::ReadVec2(XmlNode &node) { std::string val; XmlParser::getValueAsString(node, val); const char *s = val.c_str(); + const char *end = val.c_str() + val.size(); ai_real v[2] = {}; for (int i = 0; i < 2; ++i) { - if (!SkipSpaces(&s)) { + if (!SkipSpaces(&s, end)) { LogError("unexpected EOL, failed to parse vec2"); return vec; } v[i] = fast_atof(&s); - SkipSpaces(&s); + SkipSpaces(&s, end); if (i != 1 && *s != ',') { LogError("expected comma, failed to parse vec2"); return vec; @@ -757,14 +772,15 @@ aiVector3D XGLImporter::ReadVec3(XmlNode &node) { std::string v; XmlParser::getValueAsString(node, v); const char *s = v.c_str(); + const char *end = v.c_str() + v.size(); for (int i = 0; i < 3; ++i) { - if (!SkipSpaces(&s)) { + if (!SkipSpaces(&s, end)) { LogError("unexpected EOL, failed to parse vec3"); return vec; } vec[i] = fast_atof(&s); - SkipSpaces(&s); + SkipSpaces(&s, end); if (i != 2 && *s != ',') { LogError("expected comma, failed to parse vec3"); return vec; @@ -784,4 +800,6 @@ aiColor3D XGLImporter::ReadCol3(XmlNode &node) { return aiColor3D(v.x, v.y, v.z); } +} // namespace Assimp + #endif // ASSIMP_BUILD_NO_XGL_IMPORTER diff --git a/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.h b/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.h index ae7ccddc2..9d39bc811 100644 --- a/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.h +++ b/Engine/lib/assimp/code/AssetLib/XGL/XGLLoader.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -69,16 +69,20 @@ namespace Assimp { */ class XGLImporter : public BaseImporter, public LogFunctions { public: + /// @brief The class constructor. XGLImporter(); + + /// @brief The class destructor. ~XGLImporter() override; - // ------------------------------------------------------------------- - /** Returns whether the class can handle the format of the given file. - * See BaseImporter::CanRead() for details. */ + /// @brief Returns whether the class can handle the format of the given file. + /// @see BaseImporter::CanRead() for details. */ bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; protected: + void clear(); + // ------------------------------------------------------------------- /** Return importer meta information. * See #BaseImporter::GetInfo for the details */ @@ -92,10 +96,7 @@ protected: private: struct TempScope { - TempScope() : - light() { - // empty - } + TempScope() : light() {} ~TempScope() { for (aiMesh *m : meshes_linear) { @@ -145,9 +146,7 @@ private: }; struct TempMaterialMesh { - TempMaterialMesh() : - pflags(), - matid() { + TempMaterialMesh() : pflags(), matid() { // empty } @@ -160,9 +159,7 @@ private: }; struct TempFace { - TempFace() : - has_uv(), - has_normal() { + TempFace() : has_uv(), has_normal() { // empty } @@ -175,26 +172,25 @@ private: private: void Cleanup(); - std::string GetElementName(); bool ReadElement(); bool ReadElementUpToClosing(const char *closetag); bool SkipToText(); unsigned int ReadIDAttr(XmlNode &node); - void ReadWorld(XmlNode &node, TempScope &scope); void ReadLighting(XmlNode &node, TempScope &scope); aiLight *ReadDirectionalLight(XmlNode &node); aiNode *ReadObject(XmlNode &node, TempScope &scope); - bool ReadMesh(XmlNode &node, TempScope &scope, bool &empty); - void ReadMaterial(XmlNode &node, TempScope &scope); + bool ReadMesh(XmlNode &node, TempScope &scope); + void AppendOutputMeshes(std::map bymat, TempScope &scope, const unsigned int mesh_id); + unsigned int ReadVertices(XmlNode &child, TempMesh t, TempFace *tf, bool *has, unsigned int mid, TempScope &scope); + unsigned int ReadMaterial(XmlNode &node, TempScope &scope); aiVector2D ReadVec2(XmlNode &node); aiVector3D ReadVec3(XmlNode &node); aiColor3D ReadCol3(XmlNode &node); aiMatrix4x4 ReadTrafo(XmlNode &node); unsigned int ReadIndexFromText(XmlNode &node); float ReadFloat(XmlNode &node); - aiMesh *ToOutputMesh(const TempMaterialMesh &m); void ReadFaceVertex(XmlNode &node, const TempMesh &t, TempFace &out); unsigned int ResolveMaterialRef(XmlNode &node, TempScope &scope); diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.h b/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.h index a599cd5fa..27dfae005 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.h +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -260,7 +260,7 @@ public: VEC4, MAT2, MAT3, - MAT4 + MAT4 }; inline static Value FromString(const char *str) { @@ -288,8 +288,8 @@ private: }; template - struct data { - static const Info infos[NUM_VALUES]; + struct data { + static const Info infos[NUM_VALUES]; }; }; @@ -297,11 +297,11 @@ private: template const AttribType::Info AttribType::data::infos[AttribType::NUM_VALUES] = { { "SCALAR", 1 }, - { "VEC2", 2 }, - { "VEC3", 3 }, - { "VEC4", 4 }, - { "MAT2", 4 }, - { "MAT3", 9 }, + { "VEC2", 2 }, + { "VEC3", 3 }, + { "VEC4", 4 }, + { "MAT2", 4 }, + { "MAT3", 9 }, { "MAT4", 16 } }; @@ -374,7 +374,7 @@ struct Accessor : public Object { } inline bool IsValid() const { - return data != 0; + return data != nullptr; } }; @@ -513,21 +513,22 @@ struct Camera : public Object { }; Type type; + struct Perspective { + float aspectRatio; //! Add(T *obj); public: - LazyDict(Asset &asset, const char *dictId, const char *extId = 0); + LazyDict(Asset &asset, const char *dictId, const char *extId = nullptr); ~LazyDict(); Ref Get(const char *id); @@ -970,17 +967,17 @@ public: Ref scene; public: - Asset(IOSystem *io = 0) : - mIOSystem(io), - asset(), - accessors(*this, "accessors"), - animations(*this, "animations"), - buffers(*this, "buffers"), - bufferViews(*this, "bufferViews"), - cameras(*this, "cameras"), - images(*this, "images"), - materials(*this, "materials"), - meshes(*this, "meshes"), + Asset(IOSystem *io = nullptr) : + mIOSystem(io), + asset(), + accessors(*this, "accessors"), + animations(*this, "animations"), + buffers(*this, "buffers"), + bufferViews(*this, "bufferViews"), + cameras(*this, "cameras"), + images(*this, "images"), + materials(*this, "materials"), + meshes(*this, "meshes"), nodes(*this, "nodes"), samplers(*this, "samplers"), scenes(*this, "scenes"), diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.inl b/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.inl index 4eb7cd609..5e554a31e 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.inl +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFAsset.inl @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,7 +68,7 @@ using namespace glTFCommon; template inline LazyDict::LazyDict(Asset &asset, const char *dictId, const char *extId) : - mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) { + mDictId(dictId), mExtId(extId), mDict(nullptr), mAsset(asset) { asset.mDicts.push_back(this); // register to the list of dictionaries } @@ -81,7 +81,7 @@ inline LazyDict::~LazyDict() { template inline void LazyDict::AttachToDocument(Document &doc) { - Value *container = 0; + Value *container = nullptr; if (mExtId) { if (Value *exts = FindObject(doc, "extensions")) { @@ -98,7 +98,7 @@ inline void LazyDict::AttachToDocument(Document &doc) { template inline void LazyDict::DetachFromDocument() { - mDict = 0; + mDict = nullptr; } template @@ -194,7 +194,7 @@ inline void Buffer::Read(Value &obj, Asset &r) { glTFCommon::Util::DataURI dataURI; if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { if (dataURI.base64) { - uint8_t *data = 0; + uint8_t *data = nullptr; this->byteLength = Base64::Decode(dataURI.data, dataURI.dataLength, data); this->mData.reset(data, std::default_delete()); @@ -383,9 +383,9 @@ inline unsigned int Accessor::GetElementSize() { } inline uint8_t *Accessor::GetPointer() { - if (!bufferView || !bufferView->buffer) return 0; + if (!bufferView || !bufferView->buffer) return nullptr; uint8_t *basePtr = bufferView->buffer->GetPointer(); - if (!basePtr) return 0; + if (!basePtr) return nullptr; size_t offset = byteOffset + bufferView->byteOffset; @@ -698,7 +698,7 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. int undPos = 0; - Mesh::AccessorList *vec = 0; + Mesh::AccessorList *vec = nullptr; if (GetAttribVector(prim, attr, vec, undPos)) { size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; if ((*vec).size() <= idx) (*vec).resize(idx + 1); diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.h b/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.h index 6dbc42420..832c9e847 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.h +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.inl b/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.inl index a1265fb4f..c0b8edfa2 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.inl +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFAssetWriter.inl @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -525,7 +525,7 @@ namespace glTF { { std::unique_ptr jsonOutFile(mAsset.OpenFile(path, "wt", true)); - if (jsonOutFile == 0) { + if (jsonOutFile == nullptr) { throw DeadlyExportError("Could not open output file: " + std::string(path)); } @@ -548,7 +548,7 @@ namespace glTF { std::unique_ptr binOutFile(mAsset.OpenFile(binPath, "wb", true)); - if (binOutFile == 0) { + if (binOutFile == nullptr) { throw DeadlyExportError("Could not open output file: " + binPath); } @@ -564,7 +564,7 @@ namespace glTF { { std::unique_ptr outfile(mAsset.OpenFile(path, "wb", true)); - if (outfile == 0) { + if (outfile == nullptr) { throw DeadlyExportError("Could not open output file: " + std::string(path)); } diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.cpp b/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.cpp index fea680cd3..11e038fa3 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.cpp +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.h b/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.h index edc3c7e03..e42d905ff 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.h +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFCommon.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -259,7 +259,7 @@ class Ref { public: Ref() : - vector(0), + vector(nullptr), index(0) {} Ref(std::vector &vec, unsigned int idx) : vector(&vec), @@ -495,22 +495,22 @@ inline Value *FindExtension(Value &val, const char *extensionId) { inline Value *FindString(Value &val, const char *id) { Value::MemberIterator it = val.FindMember(id); - return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0; + return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : nullptr; } inline Value *FindObject(Value &val, const char *id) { Value::MemberIterator it = val.FindMember(id); - return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : 0; + return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : nullptr; } inline Value *FindArray(Value &val, const char *id) { Value::MemberIterator it = val.FindMember(id); - return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : 0; + return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : nullptr; } inline Value *FindNumber(Value &val, const char *id) { Value::MemberIterator it = val.FindMember(id); - return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0; + return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : nullptr; } } // namespace glTFCommon diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.cpp b/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.cpp index afcfb1223..0cffda024 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,6 +56,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include // Header files, standard library. #include @@ -111,7 +112,11 @@ glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiSc mScene.reset(sceneCopy_tmp); - mAsset.reset( new glTF::Asset( pIOSystem ) ); + mAsset = std::make_shared(pIOSystem); + + configEpsilon = mProperties->GetPropertyFloat( + AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON, + (ai_real)AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON_DEFAULT); if (isBinary) { mAsset->SetAsBinary(); @@ -322,7 +327,7 @@ void glTFExporter::GetTexSampler(const aiMaterial* mat, glTF::TexProperty& prop) prop.texture->sampler->minFilter = SamplerMinFilter_Linear; } -void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, +void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, const char* propName, int type, int idx, aiTextureType tt) { aiString tex; aiColor4D col; @@ -370,9 +375,9 @@ void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& pr } if (mat->Get(propName, type, idx, col) == AI_SUCCESS) { - prop.color[0] = col.r; + prop.color[0] = col.r; prop.color[1] = col.g; - prop.color[2] = col.b; + prop.color[2] = col.b; prop.color[3] = col.a; } } @@ -824,7 +829,7 @@ unsigned int glTFExporter::ExportNodeHierarchy(const aiNode* n) { Ref node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); - if (!n->mTransformation.IsIdentity()) { + if (!n->mTransformation.IsIdentity(configEpsilon)) { node->matrix.isPresent = true; CopyValue(n->mTransformation, node->matrix.value); } @@ -851,7 +856,7 @@ unsigned int glTFExporter::ExportNode(const aiNode* n, Ref& parent) node->parent = parent; - if (!n->mTransformation.IsIdentity()) { + if (!n->mTransformation.IsIdentity(configEpsilon)) { node->matrix.isPresent = true; CopyValue(n->mTransformation, node->matrix.value); } diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.h b/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.h index a52695402..adac06197 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.h +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -98,6 +99,8 @@ private: std::vector mBodyData; + ai_real configEpsilon; + void WriteBinaryData(IOStream *outfile, std::size_t sceneLength); void GetTexSampler(const aiMaterial *mat, glTF::TexProperty &prop); diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.cpp b/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.cpp index 20ff7e7c8..91f39e92b 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,11 +62,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; using namespace glTF; -// -// glTFImporter -// - -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "glTF Importer", "", "", @@ -80,7 +76,7 @@ static const aiImporterDesc desc = { }; glTFImporter::glTFImporter() : - BaseImporter(), meshOffsets(), embeddedTexIdxs(), mScene(nullptr) { + mScene(nullptr) { // empty } @@ -93,7 +89,10 @@ const aiImporterDesc *glTFImporter::GetInfo() const { bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { glTF::Asset asset(pIOHandler); try { - asset.Load(pFile, GetExtension(pFile) == "glb"); + asset.Load(pFile, + CheckMagicToken( + pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0, + static_cast(strlen(AI_GLB_MAGIC_NUMBER)))); return asset.asset; } catch (...) { return false; @@ -110,7 +109,7 @@ inline void SetMaterialColorProperty(std::vector &embeddedTexIdxs, Asset & if (texIdx != -1) { // embedded // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, AI_MAXLEN - 1, texIdx); } mat->AddProperty(&uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); @@ -243,7 +242,7 @@ void glTFImporter::ImportMeshes(glTF::Asset &r) { if (mesh.primitives.size() > 1) { ai_uint32 &len = aim->mName.length; aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(AI_MAXLEN - len - 1), p); } switch (prim.mode) { @@ -283,7 +282,7 @@ void glTFImporter::ImportMeshes(glTF::Asset &r) { } } - aiFace *faces = 0; + aiFace *faces = nullptr; unsigned int nFaces = 0; if (prim.indices) { @@ -632,8 +631,9 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) { numEmbeddedTexs += 1; } - if (numEmbeddedTexs == 0) + if (numEmbeddedTexs == 0) { return; + } mScene->mTextures = new aiTexture *[numEmbeddedTexs]; @@ -658,11 +658,13 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) { if (!img.mimeType.empty()) { const char *ext = strchr(img.mimeType.c_str(), '/') + 1; if (ext) { - if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + if (strncmp(ext, "jpeg", 4) == 0) { + ext = "jpg"; + } - size_t len = strlen(ext); + const size_t len = strlen(ext); if (len <= 3) { - strcpy(tex->achFormatHint, ext); + strncpy(tex->achFormatHint, ext, len); } } } @@ -697,7 +699,10 @@ void glTFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOS // read the asset file glTF::Asset asset(pIOHandler); - asset.Load(pFile, GetExtension(pFile) == "glb"); + asset.Load(pFile, + CheckMagicToken( + pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0, + static_cast(strlen(AI_GLB_MAGIC_NUMBER)))); // // Copy the data out diff --git a/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.h b/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.h index 529da53cc..384299b1f 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.h +++ b/Engine/lib/assimp/code/AssetLib/glTF/glTFImporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.h b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.h index 3becc4d9b..60ed368d1 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.h +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * glTF Extensions Support: * KHR_materials_pbrSpecularGlossiness full + * KHR_materials_specular full * KHR_materials_unlit full * KHR_lights_punctual full * KHR_materials_sheen full @@ -51,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * KHR_materials_transmission full * KHR_materials_volume full * KHR_materials_ior full + * KHR_materials_emissive_strength full */ #ifndef GLTF2ASSET_H_INC #define GLTF2ASSET_H_INC @@ -364,20 +366,20 @@ struct CustomExtension { ~CustomExtension() = default; - CustomExtension(const CustomExtension &other) : - name(other.name), - mStringValue(other.mStringValue), - mDoubleValue(other.mDoubleValue), - mUint64Value(other.mUint64Value), - mInt64Value(other.mInt64Value), - mBoolValue(other.mBoolValue), - mValues(other.mValues) { - // empty - } + CustomExtension(const CustomExtension &other) = default; CustomExtension& operator=(const CustomExtension&) = default; }; +//! Represents metadata in an glTF2 object +struct Extras { + std::vector mValues; + + inline bool HasExtras() const { + return !mValues.empty(); + } +}; + //! Base class for all glTF top-level objects struct Object { int index; //!< The index of this object within its property container @@ -386,7 +388,7 @@ struct Object { std::string name; //!< The user-defined name of this object CustomExtension customExtensions; - CustomExtension extras; + Extras extras; //! Objects marked as special are not exported (used to emulate the binary body buffer) virtual bool IsSpecial() const { return false; } @@ -491,7 +493,7 @@ private: public: Buffer(); - ~Buffer(); + ~Buffer() override; void Read(Value &obj, Asset &r); @@ -545,7 +547,7 @@ struct BufferView : public Object { BufferViewTarget target; //! The target that the WebGL buffer should be bound to. void Read(Value &obj, Asset &r); - uint8_t *GetPointer(size_t accOffset); + uint8_t *GetPointerAndTailSize(size_t accOffset, size_t& outTailSize); }; //! A typed view into a BufferView. A BufferView contains raw binary data. @@ -573,7 +575,7 @@ struct Accessor : public Object { inline size_t GetMaxByteSize(); template - void ExtractData(T *&outData); + size_t ExtractData(T *&outData, const std::vector *remappingIndices = nullptr); void WriteData(size_t count, const void *src_buffer, size_t src_stride); void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride); @@ -627,7 +629,7 @@ struct Accessor : public Object { std::vector data; //!< Actual data, which may be defaulted to an array of zeros or the original data, with the sparse buffer view applied on top of it. - void PopulateData(size_t numBytes, uint8_t *bytes); + void PopulateData(size_t numBytes, const uint8_t *bytes); void PatchData(unsigned int elementSize); }; }; @@ -718,6 +720,7 @@ const vec4 defaultBaseColor = { 1, 1, 1, 1 }; const vec3 defaultEmissiveFactor = { 0, 0, 0 }; const vec4 defaultDiffuseFactor = { 1, 1, 1, 1 }; const vec3 defaultSpecularFactor = { 1, 1, 1 }; +const vec3 defaultSpecularColorFactor = { 1, 1, 1 }; const vec3 defaultSheenFactor = { 0, 0, 0 }; const vec3 defaultAttenuationColor = { 1, 1, 1 }; @@ -761,6 +764,16 @@ struct PbrSpecularGlossiness { void SetDefaults(); }; +struct MaterialSpecular { + float specularFactor; + vec3 specularColorFactor; + TextureInfo specularTexture; + TextureInfo specularColorTexture; + + MaterialSpecular() { SetDefaults(); } + void SetDefaults(); +}; + struct MaterialSheen { vec3 sheenColorFactor; float sheenRoughnessFactor; @@ -801,6 +814,13 @@ struct MaterialIOR { void SetDefaults(); }; +struct MaterialEmissiveStrength { + float emissiveStrength = 0.f; + + MaterialEmissiveStrength() { SetDefaults(); } + void SetDefaults(); +}; + //! The material appearance of a primitive. struct Material : public Object { //PBR metallic roughness properties @@ -818,6 +838,9 @@ struct Material : public Object { //extension: KHR_materials_pbrSpecularGlossiness Nullable pbrSpecularGlossiness; + //extension: KHR_materials_specular + Nullable materialSpecular; + //extension: KHR_materials_sheen Nullable materialSheen; @@ -832,7 +855,10 @@ struct Material : public Object { //extension: KHR_materials_ior Nullable materialIOR; - + + //extension: KHR_materials_emissive_strength + Nullable materialEmissiveStrength; + //extension: KHR_materials_unlit bool unlit; @@ -1044,7 +1070,7 @@ class LazyDict : public LazyDictBase { Ref Add(T *obj); public: - LazyDict(Asset &asset, const char *dictId, const char *extId = 0); + LazyDict(Asset &asset, const char *dictId, const char *extId = nullptr); ~LazyDict(); Ref Retrieve(unsigned int i); @@ -1075,8 +1101,7 @@ struct AssetMetadata { void Read(Document &doc); - AssetMetadata() : - version() {} + AssetMetadata() = default; }; // @@ -1098,6 +1123,7 @@ public: //! Keeps info about the enabled extensions struct Extensions { bool KHR_materials_pbrSpecularGlossiness; + bool KHR_materials_specular; bool KHR_materials_unlit; bool KHR_lights_punctual; bool KHR_texture_transform; @@ -1106,12 +1132,14 @@ public: bool KHR_materials_transmission; bool KHR_materials_volume; bool KHR_materials_ior; + bool KHR_materials_emissive_strength; bool KHR_draco_mesh_compression; bool FB_ngon_encoding; bool KHR_texture_basisu; Extensions() : KHR_materials_pbrSpecularGlossiness(false), + KHR_materials_specular(false), KHR_materials_unlit(false), KHR_lights_punctual(false), KHR_texture_transform(false), @@ -1120,6 +1148,7 @@ public: KHR_materials_transmission(false), KHR_materials_volume(false), KHR_materials_ior(false), + KHR_materials_emissive_strength(false), KHR_draco_mesh_compression(false), FB_ngon_encoding(false), KHR_texture_basisu(false) { diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.inl b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.inl index db47915d6..bcc5b7ddb 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.inl +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Asset.inl @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -45,6 +45,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include +#include // clang-format off #ifdef ASSIMP_ENABLE_DRACO @@ -139,6 +142,18 @@ inline CustomExtension ReadExtensions(const char *name, Value &obj) { return ret; } +inline Extras ReadExtras(Value &obj) { + Extras ret; + + ret.mValues.reserve(obj.MemberCount()); + for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { + auto &val = it->value; + ret.mValues.emplace_back(ReadExtensions(it->name.GetString(), val)); + } + + return ret; +} + inline void CopyData(size_t count, const uint8_t *src, size_t src_stride, uint8_t *dst, size_t dst_stride) { if (src_stride == dst_stride) { @@ -248,7 +263,7 @@ inline void Object::ReadExtensions(Value &val) { inline void Object::ReadExtras(Value &val) { if (Value *curExtras = FindObject(val, "extras")) { - this->extras = glTF2::ReadExtensions("extras", *curExtras); + this->extras = glTF2::ReadExtras(*curExtras); } } @@ -279,6 +294,8 @@ inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Prim // Usually uint32_t but shouldn't assume if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) { memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength); + // Assign this alternate data buffer to the accessor + prim.indices->decodedBuffer.swap(decodedIndexBuffer); return; } @@ -371,7 +388,7 @@ template inline LazyDict::LazyDict(Asset &asset, const char *dictId, const char *extId) : mDictId(dictId), mExtId(extId), - mDict(0), + mDict(nullptr), mAsset(asset) { asset.mDicts.push_back(this); // register to the list of dictionaries } @@ -770,12 +787,14 @@ inline void BufferView::Read(Value &obj, Asset &r) { } } -inline uint8_t *BufferView::GetPointer(size_t accOffset) { +inline uint8_t *BufferView::GetPointerAndTailSize(size_t accOffset, size_t& outTailSize) { if (!buffer) { + outTailSize = 0; return nullptr; } - uint8_t *basePtr = buffer->GetPointer(); + uint8_t * const basePtr = buffer->GetPointer(); if (!basePtr) { + outTailSize = 0; return nullptr; } @@ -784,17 +803,25 @@ inline uint8_t *BufferView::GetPointer(size_t accOffset) { const size_t begin = buffer->EncodedRegion_Current->Offset; const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length; if ((offset >= begin) && (offset < end)) { + outTailSize = end - offset; return &buffer->EncodedRegion_Current->DecodedData[offset - begin]; } } + if (offset >= buffer->byteLength) + { + outTailSize = 0; + return nullptr; + } + + outTailSize = buffer->byteLength - offset; return basePtr + offset; } // // struct Accessor // -inline void Accessor::Sparse::PopulateData(size_t numBytes, uint8_t *bytes) { +inline void Accessor::Sparse::PopulateData(size_t numBytes, const uint8_t *bytes) { if (bytes) { data.assign(bytes, bytes + numBytes); } else { @@ -803,11 +830,21 @@ inline void Accessor::Sparse::PopulateData(size_t numBytes, uint8_t *bytes) { } inline void Accessor::Sparse::PatchData(unsigned int elementSize) { - uint8_t *pIndices = indices->GetPointer(indicesByteOffset); + size_t indicesTailDataSize; + uint8_t *pIndices = indices->GetPointerAndTailSize(indicesByteOffset, indicesTailDataSize); const unsigned int indexSize = int(ComponentTypeSize(indicesType)); uint8_t *indicesEnd = pIndices + count * indexSize; - uint8_t *pValues = values->GetPointer(valuesByteOffset); + if ((uint64_t)indicesEnd > (uint64_t)pIndices + indicesTailDataSize) { + throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory."); + } + + size_t valuesTailDataSize; + uint8_t* pValues = values->GetPointerAndTailSize(valuesByteOffset, valuesTailDataSize); + + if (elementSize * count > valuesTailDataSize) { + throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory."); + } while (pIndices != indicesEnd) { size_t offset; switch (indicesType) { @@ -879,6 +916,9 @@ inline void Accessor::Read(Value &obj, Asset &r) { if (Value *indicesValue = FindObject(*sparseValue, "indices")) { //indices bufferView Value *indiceViewID = FindUInt(*indicesValue, "bufferView"); + if (!indiceViewID) { + throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); + } sparse->indices = r.bufferViews.Retrieve(indiceViewID->GetUint()); //indices byteOffset sparse->indicesByteOffset = MemberOrDefault(*indicesValue, "byteOffset", size_t(0)); @@ -894,6 +934,9 @@ inline void Accessor::Read(Value &obj, Asset &r) { if (Value *valuesValue = FindObject(*sparseValue, "values")) { //value bufferView Value *valueViewID = FindUInt(*valuesValue, "bufferView"); + if (!valueViewID) { + throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); + } sparse->values = r.bufferViews.Retrieve(valueViewID->GetUint()); //value byteOffset sparse->valuesByteOffset = MemberOrDefault(*valuesValue, "byteOffset", size_t(0)); @@ -903,8 +946,18 @@ inline void Accessor::Read(Value &obj, Asset &r) { const unsigned int elementSize = GetElementSize(); const size_t dataSize = count * elementSize; - sparse->PopulateData(dataSize, bufferView ? bufferView->GetPointer(byteOffset) : 0); - sparse->PatchData(elementSize); + if (bufferView) { + size_t bufferViewTailSize; + const uint8_t* bufferViewPointer = bufferView->GetPointerAndTailSize(byteOffset, bufferViewTailSize); + if (dataSize > bufferViewTailSize) { + throw DeadlyImportError("Invalid buffer when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); + } + sparse->PopulateData(dataSize, bufferViewPointer); + } + else { + sparse->PopulateData(dataSize, nullptr); + } + sparse->PatchData(elementSize); } } @@ -962,14 +1015,15 @@ inline size_t Accessor::GetMaxByteSize() { } template -void Accessor::ExtractData(T *&outData) { +size_t Accessor::ExtractData(T *&outData, const std::vector *remappingIndices) { uint8_t *data = GetPointer(); if (!data) { throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name)); } + const size_t usedCount = (remappingIndices != nullptr) ? remappingIndices->size() : count; const size_t elemSize = GetElementSize(); - const size_t totalSize = elemSize * count; + const size_t totalSize = elemSize * usedCount; const size_t stride = GetStride(); @@ -980,18 +1034,31 @@ void Accessor::ExtractData(T *&outData) { } const size_t maxSize = GetMaxByteSize(); - if (count * stride > maxSize) { - throw DeadlyImportError("GLTF: count*stride ", (count * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); - } - outData = new T[count]; - if (stride == elemSize && targetElemSize == elemSize) { - memcpy(outData, data, totalSize); - } else { - for (size_t i = 0; i < count; ++i) { - memcpy(outData + i, data + i * stride, elemSize); + outData = new T[usedCount]; + + if (remappingIndices != nullptr) { + const unsigned int maxIndexCount = static_cast(maxSize / stride); + for (size_t i = 0; i < usedCount; ++i) { + size_t srcIdx = (*remappingIndices)[i]; + if (srcIdx >= maxIndexCount) { + throw DeadlyImportError("GLTF: index*stride ", (srcIdx * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); + } + memcpy(outData + i, data + srcIdx * stride, elemSize); + } + } else { // non-indexed cases + if (usedCount * stride > maxSize) { + throw DeadlyImportError("GLTF: count*stride ", (usedCount * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); + } + if (stride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } else { + for (size_t i = 0; i < usedCount; ++i) { + memcpy(outData + i, data + i * stride, elemSize); + } } } + return usedCount; } inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) { @@ -1249,6 +1316,19 @@ inline void Material::Read(Value &material, Asset &r) { this->pbrSpecularGlossiness = Nullable(pbrSG); } } + + if (r.extensionsUsed.KHR_materials_specular) { + if (Value *curMatSpecular = FindObject(*extensions, "KHR_materials_specular")) { + MaterialSpecular specular; + + ReadMember(*curMatSpecular, "specularFactor", specular.specularFactor); + ReadTextureProperty(r, *curMatSpecular, "specularTexture", specular.specularTexture); + ReadMember(*curMatSpecular, "specularColorFactor", specular.specularColorFactor); + ReadTextureProperty(r, *curMatSpecular, "specularColorTexture", specular.specularColorTexture); + + this->materialSpecular = Nullable(specular); + } + } // Extension KHR_texture_transform is handled in ReadTextureProperty @@ -1313,6 +1393,16 @@ inline void Material::Read(Value &material, Asset &r) { } } + if (r.extensionsUsed.KHR_materials_emissive_strength) { + if (Value *curMaterialEmissiveStrength = FindObject(*extensions, "KHR_materials_emissive_strength")) { + MaterialEmissiveStrength emissiveStrength; + + ReadMember(*curMaterialEmissiveStrength, "emissiveStrength", emissiveStrength.emissiveStrength); + + this->materialEmissiveStrength = Nullable(emissiveStrength); + } + } + unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit"); } } @@ -1337,6 +1427,12 @@ inline void PbrSpecularGlossiness::SetDefaults() { glossinessFactor = 1.0f; } +inline void MaterialSpecular::SetDefaults() { + //KHR_materials_specular properties + SetVector(specularColorFactor, defaultSpecularColorFactor); + specularFactor = 1.f; +} + inline void MaterialSheen::SetDefaults() { //KHR_materials_sheen properties SetVector(sheenColorFactor, defaultSheenFactor); @@ -1346,7 +1442,7 @@ inline void MaterialSheen::SetDefaults() { inline void MaterialVolume::SetDefaults() { //KHR_materials_volume properties thicknessFactor = 0.f; - attenuationDistance = INFINITY; + attenuationDistance = std::numeric_limits::infinity(); SetVector(attenuationColor, defaultAttenuationColor); } @@ -1355,6 +1451,11 @@ inline void MaterialIOR::SetDefaults() { ior = 1.5f; } +inline void MaterialEmissiveStrength::SetDefaults() { + //KHR_materials_emissive_strength properties + emissiveStrength = 0.f; +} + inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { Value *curName = FindMember(pJSON_Object, "name"); if (nullptr != curName && curName->IsString()) { @@ -1489,6 +1590,22 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { } } } + + if(this->targetNames.empty()) + { + Value *curExtras = FindObject(primitive, "extras"); + if (nullptr != curExtras) { + if (Value *curTargetNames = FindArray(*curExtras, "targetNames")) { + this->targetNames.resize(curTargetNames->Size()); + for (unsigned int j = 0; j < curTargetNames->Size(); ++j) { + Value &targetNameValue = (*curTargetNames)[j]; + if (targetNameValue.IsString()) { + this->targetNames[j] = targetNameValue.GetString(); + } + } + } + } + } } } @@ -1888,7 +2005,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) std::vector sceneData; rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); - // If a schemaDocumentProvider is available, see if the glTF schema is present. + // If a schemaDocumentProvider is available, see if the glTF schema is present. // If so, use it to validate the document. if (mSchemaDocumentProvider) { if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument("glTF.schema.json", 16)) { @@ -2018,6 +2135,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { } CHECK_EXT(KHR_materials_pbrSpecularGlossiness); + CHECK_EXT(KHR_materials_specular); CHECK_EXT(KHR_materials_unlit); CHECK_EXT(KHR_lights_punctual); CHECK_EXT(KHR_texture_transform); @@ -2026,6 +2144,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { CHECK_EXT(KHR_materials_transmission); CHECK_EXT(KHR_materials_volume); CHECK_EXT(KHR_materials_ior); + CHECK_EXT(KHR_materials_emissive_strength); CHECK_EXT(KHR_draco_mesh_compression); CHECK_EXT(KHR_texture_basisu); diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h index 089a15844..f57b6558d 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -45,12 +45,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * glTF Extensions Support: * KHR_materials_pbrSpecularGlossiness: full + * KHR_materials_specular: full * KHR_materials_unlit: full * KHR_materials_sheen: full * KHR_materials_clearcoat: full * KHR_materials_transmission: full * KHR_materials_volume: full * KHR_materials_ior: full + * KHR_materials_emissive_strength: full */ #ifndef GLTF2ASSETWRITER_H_INC #define GLTF2ASSETWRITER_H_INC diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl index 0be139595..0ca23863c 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -418,6 +418,27 @@ namespace glTF2 { exts.AddMember("KHR_materials_unlit", unlit, w.mAl); } + if (m.materialSpecular.isPresent) { + Value materialSpecular(rapidjson::Type::kObjectType); + materialSpecular.SetObject(); + + MaterialSpecular &specular = m.materialSpecular.value; + + if (specular.specularFactor != 0.0f) { + WriteFloat(materialSpecular, specular.specularFactor, "specularFactor", w.mAl); + } + if (specular.specularColorFactor[0] != defaultSpecularColorFactor[0] && specular.specularColorFactor[1] != defaultSpecularColorFactor[1] && specular.specularColorFactor[2] != defaultSpecularColorFactor[2]) { + WriteVec(materialSpecular, specular.specularColorFactor, "specularColorFactor", w.mAl); + } + + WriteTex(materialSpecular, specular.specularTexture, "specularTexture", w.mAl); + WriteTex(materialSpecular, specular.specularColorTexture, "specularColorTexture", w.mAl); + + if (!materialSpecular.ObjectEmpty()) { + exts.AddMember("KHR_materials_specular", materialSpecular, w.mAl); + } + } + if (m.materialSheen.isPresent) { Value materialSheen(rapidjson::Type::kObjectType); @@ -486,7 +507,7 @@ namespace glTF2 { WriteTex(materialVolume, volume.thicknessTexture, "thicknessTexture", w.mAl); - if (volume.attenuationDistance != INFINITY) { + if (volume.attenuationDistance != std::numeric_limits::infinity()) { WriteFloat(materialVolume, volume.attenuationDistance, "attenuationDistance", w.mAl); } @@ -511,6 +532,20 @@ namespace glTF2 { } } + if (m.materialEmissiveStrength.isPresent) { + Value materialEmissiveStrength(rapidjson::Type::kObjectType); + + MaterialEmissiveStrength &emissiveStrength = m.materialEmissiveStrength.value; + + if (emissiveStrength.emissiveStrength != 0.f) { + WriteFloat(materialEmissiveStrength, emissiveStrength.emissiveStrength, "emissiveStrength", w.mAl); + } + + if (!materialEmissiveStrength.ObjectEmpty()) { + exts.AddMember("KHR_materials_emissive_strength", materialEmissiveStrength, w.mAl); + } + } + if (!exts.ObjectEmpty()) { obj.AddMember("extensions", exts, w.mAl); } @@ -536,7 +571,7 @@ namespace glTF2 { inline void Write(Value& obj, Mesh& m, AssetWriter& w) { - /****************** Primitives *******************/ + /****************** Primitives *******************/ Value primitives; primitives.SetArray(); primitives.Reserve(unsigned(m.primitives.size()), w.mAl); @@ -620,6 +655,44 @@ namespace glTF2 { } } + inline void WriteExtrasValue(Value &parent, const CustomExtension &value, AssetWriter &w) { + Value valueNode; + + if (value.mStringValue.isPresent) { + MakeValue(valueNode, value.mStringValue.value.c_str(), w.mAl); + } else if (value.mDoubleValue.isPresent) { + MakeValue(valueNode, value.mDoubleValue.value, w.mAl); + } else if (value.mUint64Value.isPresent) { + MakeValue(valueNode, value.mUint64Value.value, w.mAl); + } else if (value.mInt64Value.isPresent) { + MakeValue(valueNode, value.mInt64Value.value, w.mAl); + } else if (value.mBoolValue.isPresent) { + MakeValue(valueNode, value.mBoolValue.value, w.mAl); + } else if (value.mValues.isPresent) { + valueNode.SetObject(); + for (auto const &subvalue : value.mValues.value) { + WriteExtrasValue(valueNode, subvalue, w); + } + } + + parent.AddMember(StringRef(value.name), valueNode, w.mAl); + } + + inline void WriteExtras(Value &obj, const Extras &extras, AssetWriter &w) { + if (!extras.HasExtras()) { + return; + } + + Value extrasNode; + extrasNode.SetObject(); + + for (auto const &value : extras.mValues) { + WriteExtrasValue(extrasNode, value, w); + } + + obj.AddMember("extras", extrasNode, w.mAl); + } + inline void Write(Value& obj, Node& n, AssetWriter& w) { if (n.matrix.isPresent) { @@ -655,6 +728,8 @@ namespace glTF2 { if(n.skeletons.size()) { AddRefsVector(obj, "skeletons", n.skeletons, w.mAl); } + + WriteExtras(obj, n.extras, w); } inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/) @@ -728,7 +803,6 @@ namespace glTF2 { } } - inline AssetWriter::AssetWriter(Asset& a) : mDoc() , mAsset(a) @@ -758,7 +832,7 @@ namespace glTF2 { { std::unique_ptr jsonOutFile(mAsset.OpenFile(path, "wt", true)); - if (jsonOutFile == 0) { + if (jsonOutFile == nullptr) { throw DeadlyExportError("Could not open output file: " + std::string(path)); } @@ -781,7 +855,7 @@ namespace glTF2 { std::unique_ptr binOutFile(mAsset.OpenFile(binPath, "wb", true)); - if (binOutFile == 0) { + if (binOutFile == nullptr) { throw DeadlyExportError("Could not open output file: " + binPath); } @@ -797,7 +871,7 @@ namespace glTF2 { { std::unique_ptr outfile(mAsset.OpenFile(path, "wb", true)); - if (outfile == 0) { + if (outfile == nullptr) { throw DeadlyExportError("Could not open output file: " + std::string(path)); } @@ -822,7 +896,7 @@ namespace glTF2 { throw DeadlyExportError("Failed to write scene data!"); } - uint32_t jsonChunkLength = (docBuffer.GetSize() + 3) & ~3; // Round up to next multiple of 4 + uint32_t jsonChunkLength = static_cast((docBuffer.GetSize() + 3) & ~3); // Round up to next multiple of 4 auto paddingLength = jsonChunkLength - docBuffer.GetSize(); GLB_Chunk jsonChunk; @@ -848,7 +922,7 @@ namespace glTF2 { int GLB_Chunk_count = 1; uint32_t binaryChunkLength = 0; if (bodyBuffer->byteLength > 0) { - binaryChunkLength = (bodyBuffer->byteLength + 3) & ~3; // Round up to next multiple of 4 + binaryChunkLength = static_cast((bodyBuffer->byteLength + 3) & ~3); // Round up to next multiple of 4 auto curPaddingLength = binaryChunkLength - bodyBuffer->byteLength; ++GLB_Chunk_count; @@ -866,7 +940,7 @@ namespace glTF2 { if (outfile->Write(bodyBuffer->GetPointer(), 1, bodyBuffer->byteLength) != bodyBuffer->byteLength) { throw DeadlyExportError("Failed to write body data!"); } - if (curPaddingLength && outfile->Write(&padding, 1, paddingLength) != paddingLength) { + if (curPaddingLength && outfile->Write(&padding, 1, curPaddingLength) != curPaddingLength) { throw DeadlyExportError("Failed to write body data padding!"); } } @@ -915,6 +989,10 @@ namespace glTF2 { exts.PushBack(StringRef("KHR_materials_unlit"), mAl); } + if (this->mAsset.extensionsUsed.KHR_materials_specular) { + exts.PushBack(StringRef("KHR_materials_specular"), mAl); + } + if (this->mAsset.extensionsUsed.KHR_materials_sheen) { exts.PushBack(StringRef("KHR_materials_sheen"), mAl); } @@ -935,6 +1013,10 @@ namespace glTF2 { exts.PushBack(StringRef("KHR_materials_ior"), mAl); } + if (this->mAsset.extensionsUsed.KHR_materials_emissive_strength) { + exts.PushBack(StringRef("KHR_materials_emissive_strength"), mAl); + } + if (this->mAsset.extensionsUsed.FB_ngon_encoding) { exts.PushBack(StringRef("FB_ngon_encoding"), mAl); } @@ -962,7 +1044,7 @@ namespace glTF2 { if (d.mObjs.empty()) return; Value* container = &mDoc; - const char* context = "Document"; + const char* context = "Document"; if (d.mExtId) { Value* exts = FindObject(mDoc, "extensions"); diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp index 3bfe49fba..c3882290b 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,11 +55,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include // Header files, standard library. #include #include #include +#include using namespace rapidjson; @@ -90,6 +92,10 @@ glTF2Exporter::glTF2Exporter(const char *filename, IOSystem *pIOSystem, const ai // Always on as our triangulation process is aware of this type of encoding mAsset->extensionsUsed.FB_ngon_encoding = true; + configEpsilon = mProperties->GetPropertyFloat( + AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON, + (ai_real)AI_CONFIG_CHECK_IDENTITY_MATRIX_EPSILON_DEFAULT); + if (isBinary) { mAsset->SetAsBinary(); } @@ -172,22 +178,6 @@ static void IdentityMatrix4(mat4 &o) { o[15] = 1; } -static bool IsBoneWeightFitted(vec4 &weight) { - return weight[0] + weight[1] + weight[2] + weight[3] >= 1.f; -} - -static int FitBoneWeight(vec4 &weight, float value) { - int i = 0; - for (; i < 4; ++i) { - if (weight[i] < value) { - weight[i] = value; - return i; - } - } - - return -1; -} - template void SetAccessorRange(Ref acc, void *data, size_t count, unsigned int numCompsIn, unsigned int numCompsOut) { @@ -263,7 +253,7 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn, for (short idx = 0; bufferData_ptr < bufferData_end; idx += 1, bufferData_ptr += numCompsIn) { bool bNonZero = false; - //for the data, check any component Non Zero + // for the data, check any component Non Zero for (unsigned int j = 0; j < numCompsOut; j++) { double valueData = bufferData_ptr[j]; double valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0; @@ -273,11 +263,11 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn, } } - //all zeros, continue + // all zeros, continue if (!bNonZero) continue; - //non zero, store the data + // non zero, store the data for (unsigned int j = 0; j < numCompsOut; j++) { T valueData = bufferData_ptr[j]; T valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0; @@ -286,14 +276,14 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn, vNZIdx.push_back(idx); } - //avoid all-0, put 1 item + // avoid all-0, put 1 item if (vNZDiff.size() == 0) { for (unsigned int j = 0; j < numCompsOut; j++) vNZDiff.push_back(0); vNZIdx.push_back(0); } - //process data + // process data outputNZDiff = new T[vNZDiff.size()]; memcpy(outputNZDiff, vNZDiff.data(), vNZDiff.size() * sizeof(T)); @@ -321,7 +311,7 @@ inline size_t NZDiff(ComponentType compType, void *data, void *dataBase, size_t } inline Ref ExportDataSparse(Asset &a, std::string &meshName, Ref &buffer, - size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE, void *dataBase = 0) { + size_t count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE, void *dataBase = nullptr) { if (!count || !data) { return Ref(); } @@ -356,12 +346,12 @@ inline Ref ExportDataSparse(Asset &a, std::string &meshName, Reftype = typeOut; if (data) { - void *nzDiff = 0, *nzIdx = 0; + void *nzDiff = nullptr, *nzIdx = nullptr; size_t nzCount = NZDiff(compType, data, dataBase, count, numCompsIn, numCompsOut, nzDiff, nzIdx); acc->sparse.reset(new Accessor::Sparse); acc->sparse->count = nzCount; - //indices + // indices unsigned int bytesPerIdx = sizeof(unsigned short); size_t indices_offset = buffer->byteLength; size_t indices_padding = indices_offset % bytesPerIdx; @@ -379,7 +369,7 @@ inline Ref ExportDataSparse(Asset &a, std::string &meshName, Refsparse->indicesByteOffset = 0; acc->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx); - //values + // values size_t values_offset = buffer->byteLength; size_t values_padding = values_offset % bytesPerComp; values_offset += values_padding; @@ -395,9 +385,9 @@ inline Ref ExportDataSparse(Asset &a, std::string &meshName, Refsparse->valuesByteOffset = 0; acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp); - //clear - delete[](char *) nzDiff; - delete[](char *) nzIdx; + // clear + delete[] (char *)nzDiff; + delete[] (char *)nzIdx; } return acc; } @@ -443,6 +433,61 @@ inline Ref ExportData(Asset &a, std::string &meshName, Ref &bu return acc; } +inline void ExportNodeExtras(const aiMetadataEntry &metadataEntry, aiString name, CustomExtension &value) { + + value.name = name.C_Str(); + switch (metadataEntry.mType) { + case AI_BOOL: + value.mBoolValue.value = *static_cast(metadataEntry.mData); + value.mBoolValue.isPresent = true; + break; + case AI_INT32: + value.mInt64Value.value = *static_cast(metadataEntry.mData); + value.mInt64Value.isPresent = true; + break; + case AI_UINT64: + value.mUint64Value.value = *static_cast(metadataEntry.mData); + value.mUint64Value.isPresent = true; + break; + case AI_FLOAT: + value.mDoubleValue.value = *static_cast(metadataEntry.mData); + value.mDoubleValue.isPresent = true; + break; + case AI_DOUBLE: + value.mDoubleValue.value = *static_cast(metadataEntry.mData); + value.mDoubleValue.isPresent = true; + break; + case AI_AISTRING: + value.mStringValue.value = static_cast(metadataEntry.mData)->C_Str(); + value.mStringValue.isPresent = true; + break; + case AI_AIMETADATA: { + const aiMetadata *subMetadata = static_cast(metadataEntry.mData); + value.mValues.value.resize(subMetadata->mNumProperties); + value.mValues.isPresent = true; + + for (unsigned i = 0; i < subMetadata->mNumProperties; ++i) { + ExportNodeExtras(subMetadata->mValues[i], subMetadata->mKeys[i], value.mValues.value.at(i)); + } + break; + } + default: + // AI_AIVECTOR3D not handled + break; + } +} + +inline void ExportNodeExtras(const aiMetadata *metadata, Extras &extras) { + if (metadata == nullptr || metadata->mNumProperties == 0) { + return; + } + + extras.mValues.resize(metadata->mNumProperties); + for (unsigned int i = 0; i < metadata->mNumProperties; ++i) { + ExportNodeExtras(metadata->mValues[i], metadata->mKeys[i], extras.mValues.at(i)); + } +} + inline void SetSamplerWrap(SamplerWrap &wrap, aiTextureMapMode map) { switch (map) { case aiTextureMapMode_Clamp: @@ -516,11 +561,15 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref &texture, unsi if (mat.GetTextureCount(tt) == 0) { return; } - + aiString tex; // Read texcoord (UV map index) - mat.Get(AI_MATKEY_UVWSRC(tt, slot), texCoord); + // Note: must be an int to be successful. + int tmp = 0; + const auto ok = mat.Get(AI_MATKEY_UVWSRC(tt, slot), tmp); + if (ok == aiReturn_SUCCESS) texCoord = tmp; + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); @@ -544,7 +593,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref &texture, unsi if (curTex != nullptr) { // embedded texture->source->name = curTex->mFilename.C_Str(); - //basisu: embedded ktx2, bu + // basisu: embedded ktx2, bu if (curTex->achFormatHint[0]) { std::string mimeType = "image/"; if (memcmp(curTex->achFormatHint, "jpg", 3) == 0) @@ -564,7 +613,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref &texture, unsi } // The asset has its own buffer, see Image::SetData - //basisu: "image/ktx2", "image/basis" as is + // basisu: "image/ktx2", "image/basis" as is texture->source->SetData(reinterpret_cast(curTex->pcData), curTex->mWidth, *mAsset); } else { texture->source->uri = path; @@ -574,7 +623,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref &texture, unsi } } - //basisu + // basisu if (useBasisUniversal) { mAsset->extensionsUsed.KHR_texture_basisu = true; mAsset->extensionsRequired.KHR_texture_basisu = true; @@ -597,7 +646,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, NormalTextureInfo &prop, ai GetMatTex(mat, texture, prop.texCoord, tt, slot); if (texture) { - //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); GetMatTexProp(mat, prop.scale, "scale", tt, slot); } } @@ -608,7 +657,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, OcclusionTextureInfo &prop, GetMatTex(mat, texture, prop.texCoord, tt, slot); if (texture) { - //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); GetMatTexProp(mat, prop.strength, "strength", tt, slot); } } @@ -640,11 +689,10 @@ aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec3 &prop, const cha return result; } +// This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default. bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) { bool result = false; // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension - // NOTE: This extension is being considered for deprecation (Dec 2020), may be replaced by KHR_material_specular - if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) { result = true; } else { @@ -674,6 +722,25 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo return result; } +bool glTF2Exporter::GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular) { + // Specular requires either/or, default factors of zero disables specular, so do not export + if (GetMatColor(mat, specular.specularColorFactor, AI_MATKEY_COLOR_SPECULAR) != AI_SUCCESS && mat.Get(AI_MATKEY_SPECULAR_FACTOR, specular.specularFactor) != AI_SUCCESS) { + return false; + } + // The spec states that the default is 1.0 and [1.0, 1.0, 1.0]. We if both are 0, which should disable specular. Otherwise, if one is 0, set to 1.0 + const bool colorFactorIsZero = specular.specularColorFactor[0] == defaultSpecularColorFactor[0] && specular.specularColorFactor[1] == defaultSpecularColorFactor[1] && specular.specularColorFactor[2] == defaultSpecularColorFactor[2]; + if (specular.specularFactor == 0.0f && colorFactorIsZero) { + return false; + } else if (specular.specularFactor == 0.0f) { + specular.specularFactor = 1.0f; + } else if (colorFactorIsZero) { + specular.specularColorFactor[0] = specular.specularColorFactor[1] = specular.specularColorFactor[2] = 1.0f; + } + GetMatTex(mat, specular.specularTexture, aiTextureType_SPECULAR, 0); + GetMatTex(mat, specular.specularColorTexture, aiTextureType_SPECULAR, 1); + return true; +} + bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) { // Return true if got any valid Sheen properties or textures if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) { @@ -733,6 +800,10 @@ bool glTF2Exporter::GetMatIOR(const aiMaterial &mat, glTF2::MaterialIOR &ior) { return mat.Get(AI_MATKEY_REFRACTI, ior.ior) == aiReturn_SUCCESS; } +bool glTF2Exporter::GetMatEmissiveStrength(const aiMaterial &mat, glTF2::MaterialEmissiveStrength &emissiveStrength) { + return mat.Get(AI_MATKEY_EMISSIVE_INTENSITY, emissiveStrength.emissiveStrength) == aiReturn_SUCCESS; +} + void glTF2Exporter::ExportMaterials() { aiString aiName; for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { @@ -755,20 +826,30 @@ void glTF2Exporter::ExportMaterials() { GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR); if (!m->pbrMetallicRoughness.baseColorTexture.texture) { - //if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture + // if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_DIFFUSE); } - GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_DIFFUSE_ROUGHNESS); + + if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) { + // if there wasn't a aiTextureType_DIFFUSE_ROUGHNESS defined in the source, fallback to aiTextureType_METALNESS + GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_METALNESS); + } + + if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) { + // if there still wasn't a aiTextureType_METALNESS defined in the source, fallback to AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE + GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + } if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) { // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material. - //a fallback to any diffuse color should be used instead + // a fallback to any diffuse color should be used instead GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { - //if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0 + // if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0 m->pbrMetallicRoughness.metallicFactor = 0; } @@ -781,10 +862,10 @@ void glTF2Exporter::ExportMaterials() { if (mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { // convert specular color to luminance float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f; - //normalize shininess (assuming max is 1000) with an inverse exponentional curve + // normalize shininess (assuming max is 1000) with an inverse exponentional curve float normalizedShininess = std::sqrt(shininess / 1000); - //clamp the shininess value between 0 and 1 + // clamp the shininess value between 0 and 1 normalizedShininess = std::min(std::max(normalizedShininess, 0.0f), 1.0f); // low specular intensity values should produce a rough material even if shininess is high. normalizedShininess = normalizedShininess * specularIntensity; @@ -814,9 +895,9 @@ void glTF2Exporter::ExportMaterials() { m->alphaMode = alphaMode.C_Str(); } - { + // This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default. + if (mProperties->GetPropertyBool(AI_CONFIG_USE_GLTF_PBR_SPECULAR_GLOSSINESS)) { // KHR_materials_pbrSpecularGlossiness extension - // NOTE: This extension is being considered for deprecation (Dec 2020) PbrSpecularGlossiness pbrSG; if (GetMatSpecGloss(mat, pbrSG)) { mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; @@ -833,7 +914,13 @@ void glTF2Exporter::ExportMaterials() { } else { // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness if (!m->pbrSpecularGlossiness.isPresent) { - // Sheen + MaterialSpecular specular; + if (GetMatSpecular(mat, specular)) { + mAsset->extensionsUsed.KHR_materials_specular = true; + m->materialSpecular = Nullable(specular); + GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); + } + MaterialSheen sheen; if (GetMatSheen(mat, sheen)) { mAsset->extensionsUsed.KHR_materials_sheen = true; @@ -851,18 +938,24 @@ void glTF2Exporter::ExportMaterials() { mAsset->extensionsUsed.KHR_materials_transmission = true; m->materialTransmission = Nullable(transmission); } - + MaterialVolume volume; if (GetMatVolume(mat, volume)) { mAsset->extensionsUsed.KHR_materials_volume = true; m->materialVolume = Nullable(volume); } - + MaterialIOR ior; if (GetMatIOR(mat, ior)) { mAsset->extensionsUsed.KHR_materials_ior = true; m->materialIOR = Nullable(ior); } + + MaterialEmissiveStrength emissiveStrength; + if (GetMatEmissiveStrength(mat, emissiveStrength)) { + mAsset->extensionsUsed.KHR_materials_emissive_strength = true; + m->materialEmissiveStrength = Nullable(emissiveStrength); + } } } } @@ -911,23 +1004,29 @@ Ref FindSkeletonRootJoint(Ref &skinRef) { return parentNodeRef; } -void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref &meshRef, Ref &bufferRef, Ref &skinRef, - std::vector &inverseBindMatricesData) { +struct boneIndexWeightPair { + unsigned int indexJoint; + float weight; + bool operator()(boneIndexWeightPair &a, boneIndexWeightPair &b) { + return a.weight > b.weight; + } +}; + +void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref &meshRef, Ref &bufferRef, Ref &skinRef, + std::vector &inverseBindMatricesData, bool unlimitedBonesPerVertex) { if (aimesh->mNumBones < 1) { return; } // Store the vertex joint and weight data. const size_t NumVerts(aimesh->mNumVertices); - vec4 *vertexJointData = new vec4[NumVerts]; - vec4 *vertexWeightData = new vec4[NumVerts]; int *jointsPerVertex = new int[NumVerts]; + std::vector> allVerticesPairs; + int maxJointsPerVertex = 0; for (size_t i = 0; i < NumVerts; ++i) { jointsPerVertex[i] = 0; - for (size_t j = 0; j < 4; ++j) { - vertexJointData[i][j] = 0; - vertexWeightData[i][j] = 0; - } + std::vector vertexPair; + allVerticesPairs.push_back(vertexPair); } for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) { @@ -957,61 +1056,88 @@ void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref &meshRef, Ref(inverseBindMatricesData.size() - 1); } - // aib->mWeights =====> vertexWeightData - for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) { + // aib->mWeights =====> temp pairs data + for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; + ++idx_weights) { unsigned int vertexId = aib->mWeights[idx_weights].mVertexId; float vertWeight = aib->mWeights[idx_weights].mWeight; - - // A vertex can only have at most four joint weights, which ideally sum up to 1 - if (IsBoneWeightFitted(vertexWeightData[vertexId])) { - continue; - } - if (jointsPerVertex[vertexId] > 3) { - int boneIndexFitted = FitBoneWeight(vertexWeightData[vertexId], vertWeight); - if (boneIndexFitted != -1) { - vertexJointData[vertexId][boneIndexFitted] = static_cast(jointNamesIndex); - } - }else { - vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast(jointNamesIndex); - vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight; - - jointsPerVertex[vertexId] += 1; - } + allVerticesPairs[vertexId].push_back({jointNamesIndex, vertWeight}); + jointsPerVertex[vertexId] += 1; + maxJointsPerVertex = + std::max(maxJointsPerVertex, jointsPerVertex[vertexId]); } - } // End: for-loop mNumMeshes - Mesh::Primitive &p = meshRef->primitives.back(); - Ref vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, - vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); - if (vertexJointAccessor) { - size_t offset = vertexJointAccessor->bufferView->byteOffset; - size_t bytesLen = vertexJointAccessor->bufferView->byteLength; - unsigned int s_bytesPerComp = ComponentTypeSize(ComponentType_UNSIGNED_SHORT); - unsigned int bytesPerComp = ComponentTypeSize(vertexJointAccessor->componentType); - size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp; - Ref buf = vertexJointAccessor->bufferView->buffer; - uint8_t *arrys = new uint8_t[bytesLen]; - unsigned int i = 0; - for (unsigned int j = 0; j < bytesLen; j += bytesPerComp) { - size_t len_p = offset + j; - float f_value = *(float *)&buf->GetPointer()[len_p]; - unsigned short c = static_cast(f_value); - memcpy(&arrys[i * s_bytesPerComp], &c, s_bytesPerComp); - ++i; - } - buf->ReplaceData_joint(offset, bytesLen, arrys, bytesLen); - vertexJointAccessor->componentType = ComponentType_UNSIGNED_SHORT; - vertexJointAccessor->bufferView->byteLength = s_bytesLen; - - p.attributes.joint.push_back(vertexJointAccessor); - delete[] arrys; + if (!unlimitedBonesPerVertex){ + // skinning limited only for 4 bones per vertex, default + maxJointsPerVertex = 4; } - Ref vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, - vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); - if (vertexWeightAccessor) { - p.attributes.weight.push_back(vertexWeightAccessor); + // temp pairs data =====> vertexWeightData + size_t numGroups = (maxJointsPerVertex - 1) / 4 + 1; + vec4 *vertexJointData = new vec4[NumVerts * numGroups]; + vec4 *vertexWeightData = new vec4[NumVerts * numGroups]; + for (size_t indexVertex = 0; indexVertex < NumVerts; ++indexVertex) { + // order pairs by weight for each vertex + std::sort(allVerticesPairs[indexVertex].begin(), + allVerticesPairs[indexVertex].end(), + boneIndexWeightPair()); + for (size_t indexGroup = 0; indexGroup < numGroups; ++indexGroup) { + for (size_t indexJoint = 0; indexJoint < 4; ++indexJoint) { + size_t indexBone = indexGroup * 4 + indexJoint; + size_t indexData = indexVertex + NumVerts * indexGroup; + if (indexBone >= allVerticesPairs[indexVertex].size()) { + vertexJointData[indexData][indexJoint] = 0.f; + vertexWeightData[indexData][indexJoint] = 0.f; + } else { + vertexJointData[indexData][indexJoint] = + static_cast( + allVerticesPairs[indexVertex][indexBone].indexJoint); + vertexWeightData[indexData][indexJoint] = + allVerticesPairs[indexVertex][indexBone].weight; + } + } + } + } + + for (size_t idx_group = 0; idx_group < numGroups; ++idx_group) { + Mesh::Primitive &p = meshRef->primitives.back(); + Ref vertexJointAccessor = ExportData( + mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, + vertexJointData + idx_group * NumVerts, + AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if (vertexJointAccessor) { + size_t offset = vertexJointAccessor->bufferView->byteOffset; + size_t bytesLen = vertexJointAccessor->bufferView->byteLength; + unsigned int s_bytesPerComp = + ComponentTypeSize(ComponentType_UNSIGNED_SHORT); + unsigned int bytesPerComp = + ComponentTypeSize(vertexJointAccessor->componentType); + size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp; + Ref buf = vertexJointAccessor->bufferView->buffer; + uint8_t *arrys = new uint8_t[bytesLen]; + unsigned int i = 0; + for (unsigned int j = 0; j < bytesLen; j += bytesPerComp) { + size_t len_p = offset + j; + float f_value = *(float *)&buf->GetPointer()[len_p]; + unsigned short c = static_cast(f_value); + memcpy(&arrys[i * s_bytesPerComp], &c, s_bytesPerComp); + ++i; + } + buf->ReplaceData_joint(offset, bytesLen, arrys, bytesLen); + vertexJointAccessor->componentType = ComponentType_UNSIGNED_SHORT; + vertexJointAccessor->bufferView->byteLength = s_bytesLen; + + p.attributes.joint.push_back(vertexJointAccessor); + delete[] arrys; + } + Ref vertexWeightAccessor = ExportData( + mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, + vertexWeightData + idx_group * NumVerts, + AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if (vertexWeightAccessor) { + p.attributes.weight.push_back(vertexWeightAccessor); + } } delete[] jointsPerVertex; delete[] vertexWeightData; @@ -1052,6 +1178,9 @@ void glTF2Exporter::ExportMeshes() { for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { const aiMesh *aim = mScene->mMeshes[idx_mesh]; + if (aim->mNumFaces == 0) { + continue; + } std::string name = aim->mName.C_Str(); @@ -1067,7 +1196,7 @@ void glTF2Exporter::ExportMeshes() { /******************* Vertices ********************/ Ref v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, - AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); if (v) { p.attributes.position.push_back(v); } @@ -1080,8 +1209,8 @@ void glTF2Exporter::ExportMeshes() { } } - Ref n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, - AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + Ref n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, + AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); if (n) { p.attributes.normal.push_back(n); } @@ -1102,8 +1231,8 @@ void glTF2Exporter::ExportMeshes() { if (aim->mNumUVComponents[i] > 0) { AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3; - Ref tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], - AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + Ref tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], + AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); if (tc) { p.attributes.texcoord.push_back(tc); } @@ -1113,7 +1242,7 @@ void glTF2Exporter::ExportMeshes() { /*************** Vertex colors ****************/ for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++indexColorChannel) { Ref c = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mColors[indexColorChannel], - AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); if (c) { p.attributes.color.push_back(c); } @@ -1130,8 +1259,8 @@ void glTF2Exporter::ExportMeshes() { } } - p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR, - ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER); + p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR, + ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER); } switch (aim->mPrimitiveTypes) { @@ -1149,9 +1278,19 @@ void glTF2Exporter::ExportMeshes() { break; } +// /*************** Skins ****************/ +// if (aim->HasBones()) { +// ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); +// } /*************** Skins ****************/ if (aim->HasBones()) { - ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); + bool unlimitedBonesPerVertex = + this->mProperties->HasPropertyBool( + AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX) && + this->mProperties->GetPropertyBool( + AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX); + ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData, + unlimitedBonesPerVertex); } /*************** Targets for blendshapes ****************/ @@ -1274,24 +1413,24 @@ void glTF2Exporter::MergeMeshes() { unsigned int nMeshes = static_cast(node->meshes.size()); - //skip if it's 1 or less meshes per node + // skip if it's 1 or less meshes per node if (nMeshes > 1) { Ref firstMesh = node->meshes.at(0); - //loop backwards to allow easy removal of a mesh from a node once it's merged + // loop backwards to allow easy removal of a mesh from a node once it's merged for (unsigned int m = nMeshes - 1; m >= 1; --m) { Ref mesh = node->meshes.at(m); - //append this mesh's primitives to the first mesh's primitives + // append this mesh's primitives to the first mesh's primitives firstMesh->primitives.insert( firstMesh->primitives.end(), mesh->primitives.begin(), mesh->primitives.end()); - //remove the mesh from the list of meshes + // remove the mesh from the list of meshes unsigned int removedIndex = mAsset->meshes.Remove(mesh->id.c_str()); - //find the presence of the removed mesh in other nodes + // find the presence of the removed mesh in other nodes for (unsigned int nn = 0; nn < mAsset->nodes.Size(); ++nn) { Ref curNode = mAsset->nodes.Get(nn); @@ -1310,7 +1449,7 @@ void glTF2Exporter::MergeMeshes() { } } - //since we were looping backwards, reverse the order of merged primitives to their original order + // since we were looping backwards, reverse the order of merged primitives to their original order std::reverse(firstMesh->primitives.begin() + 1, firstMesh->primitives.end()); } } @@ -1325,7 +1464,7 @@ unsigned int glTF2Exporter::ExportNodeHierarchy(const aiNode *n) { node->name = n->mName.C_Str(); - if (!n->mTransformation.IsIdentity()) { + if (!n->mTransformation.IsIdentity(configEpsilon)) { node->matrix.isPresent = true; CopyValue(n->mTransformation, node->matrix.value); } @@ -1353,7 +1492,9 @@ unsigned int glTF2Exporter::ExportNode(const aiNode *n, Ref &parent) { node->parent = parent; node->name = name; - if (!n->mTransformation.IsIdentity()) { + ExportNodeExtras(n->mMetaData, node->extras); + + if (!n->mTransformation.IsIdentity(configEpsilon)) { if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) { aiQuaternion quaternion; n->mTransformation.Decompose(*reinterpret_cast(&node->scale.value), quaternion, *reinterpret_cast(&node->translation.value)); @@ -1435,9 +1576,9 @@ inline void ExtractTranslationSampler(Asset &asset, std::string &animId, RefmPositionKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 3) + 0] = (ai_real) key.mValue.x; - values[(i * 3) + 1] = (ai_real) key.mValue.y; - values[(i * 3) + 2] = (ai_real) key.mValue.z; + values[(i * 3) + 0] = (ai_real)key.mValue.x; + values[(i * 3) + 1] = (ai_real)key.mValue.y; + values[(i * 3) + 2] = (ai_real)key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1454,9 +1595,9 @@ inline void ExtractScaleSampler(Asset &asset, std::string &animId, Ref & const aiVectorKey &key = nodeChannel->mScalingKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 3) + 0] = (ai_real) key.mValue.x; - values[(i * 3) + 1] = (ai_real) key.mValue.y; - values[(i * 3) + 2] = (ai_real) key.mValue.z; + values[(i * 3) + 0] = (ai_real)key.mValue.x; + values[(i * 3) + 1] = (ai_real)key.mValue.y; + values[(i * 3) + 2] = (ai_real)key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1473,10 +1614,10 @@ inline void ExtractRotationSampler(Asset &asset, std::string &animId, RefmRotationKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 4) + 0] = (ai_real) key.mValue.x; - values[(i * 4) + 1] = (ai_real) key.mValue.y; - values[(i * 4) + 2] = (ai_real) key.mValue.z; - values[(i * 4) + 3] = (ai_real) key.mValue.w; + values[(i * 4) + 0] = (ai_real)key.mValue.x; + values[(i * 4) + 1] = (ai_real)key.mValue.y; + values[(i * 4) + 2] = (ai_real)key.mValue.z; + values[(i * 4) + 3] = (ai_real)key.mValue.w; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.h b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.h index 99425228e..27e187854 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.h +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Exporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -76,11 +77,13 @@ struct OcclusionTextureInfo; struct Node; struct Texture; struct PbrSpecularGlossiness; +struct MaterialSpecular; struct MaterialSheen; struct MaterialClearcoat; struct MaterialTransmission; struct MaterialVolume; struct MaterialIOR; +struct MaterialEmissiveStrength; // Vec/matrix types, as raw float arrays typedef float(vec2)[2]; @@ -116,11 +119,13 @@ protected: aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec4 &prop, const char *propName, int type, int idx) const; aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec3 &prop, const char *propName, int type, int idx) const; bool GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG); + bool GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular); bool GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen); bool GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat); bool GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission); bool GetMatVolume(const aiMaterial &mat, glTF2::MaterialVolume &volume); bool GetMatIOR(const aiMaterial &mat, glTF2::MaterialIOR &ior); + bool GetMatEmissiveStrength(const aiMaterial &mat, glTF2::MaterialEmissiveStrength &emissiveStrength); void ExportMetadata(); void ExportMaterials(); void ExportMeshes(); @@ -138,6 +143,7 @@ private: std::map mTexturesByPath; std::shared_ptr mAsset; std::vector mBodyData; + ai_real configEpsilon; }; } // namespace Assimp diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.cpp b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.cpp index 947edc8d5..008c0b53d 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -82,7 +82,7 @@ struct Tangent { // glTF2Importer // -static const aiImporterDesc desc = { +static constexpr aiImporterDesc desc = { "glTF2 Importer", "", "", @@ -92,32 +92,31 @@ static const aiImporterDesc desc = { 0, 0, 0, - "gltf glb" + "gltf glb vrm" }; glTF2Importer::glTF2Importer() : - BaseImporter(), - meshOffsets(), - mEmbeddedTexIdxs(), mScene(nullptr) { // empty } -glTF2Importer::~glTF2Importer() = default; - const aiImporterDesc *glTF2Importer::GetInfo() const { return &desc; } bool glTF2Importer::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig) const { const std::string extension = GetExtension(filename); - if (!checkSig && (extension != "gltf") && (extension != "glb")) { + if (!checkSig && (extension != "gltf") && (extension != "glb") && (extension != "vrm")) { return false; } if (pIOHandler) { glTF2::Asset asset(pIOHandler); - return asset.CanRead(filename, extension == "glb"); + return asset.CanRead( + filename, + CheckMagicToken( + pIOHandler, filename, AI_GLB_MAGIC_NUMBER, 1, 0, + static_cast(strlen(AI_GLB_MAGIC_NUMBER)))); } return false; @@ -162,7 +161,7 @@ static void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset if (texIdx != -1) { // embedded // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, AI_MAXLEN - 1, texIdx); } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); @@ -185,7 +184,6 @@ static void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset const ai_real rsin(sin(-transform.mRotation)); transform.mTranslation.x = (static_cast(0.5) * transform.mScaling.x) * (-rcos + rsin + 1) + prop.TextureTransformExt_t.offset[0]; transform.mTranslation.y = ((static_cast(0.5) * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y - prop.TextureTransformExt_t.offset[1]; - ; mat->AddProperty(&transform, 1, _AI_MATKEY_UVTRANSFORM_BASE, texType, texSlot); } @@ -236,7 +234,8 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); if (prop.texture && prop.texture->source) { - mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); + std::string textureStrengthKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + "strength"; + mat->AddProperty(&prop.strength, 1, textureStrengthKey.c_str(), texType, texSlot); } } @@ -282,8 +281,19 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); + // KHR_materials_specular + if (mat.materialSpecular.isPresent) { + MaterialSpecular &specular = mat.materialSpecular.value; + // Default values of zero disables Specular + if (std::memcmp(specular.specularColorFactor, defaultSpecularColorFactor, sizeof(glTFCommon::vec3)) != 0 || specular.specularFactor != 0.0f) { + SetMaterialColorProperty(r, specular.specularColorFactor, aimat, AI_MATKEY_COLOR_SPECULAR); + aimat->AddProperty(&specular.specularFactor, 1, AI_MATKEY_SPECULAR_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, specular.specularTexture, aimat, aiTextureType_SPECULAR, 0); + SetMaterialTextureProperty(embeddedTexIdxs, r, specular.specularColorTexture, aimat, aiTextureType_SPECULAR, 1); + } + } // pbrSpecularGlossiness - if (mat.pbrSpecularGlossiness.isPresent) { + else if (mat.pbrSpecularGlossiness.isPresent) { PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); @@ -357,6 +367,13 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&ior.ior, 1, AI_MATKEY_REFRACTI); } + // KHR_materials_emissive_strength + if (mat.materialEmissiveStrength.isPresent) { + MaterialEmissiveStrength &emissiveStrength = mat.materialEmissiveStrength.value; + + aimat->AddProperty(&emissiveStrength.emissiveStrength, 1, AI_MATKEY_EMISSIVE_INTENSITY); + } + return aimat; } catch (...) { delete aimat; @@ -429,10 +446,10 @@ static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsign #endif // ASSIMP_BUILD_DEBUG template -aiColor4D *GetVertexColorsForType(Ref input) { +aiColor4D *GetVertexColorsForType(Ref input, std::vector *vertexRemappingTable) { constexpr float max = std::numeric_limits::max(); aiColor4t *colors; - input->ExtractData(colors); + input->ExtractData(colors, vertexRemappingTable); auto output = new aiColor4D[input->count]; for (size_t i = 0; i < input->count; i++) { output[i] = aiColor4D( @@ -447,18 +464,73 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { ASSIMP_LOG_DEBUG("Importing ", r.meshes.Size(), " meshes"); std::vector> meshes; - unsigned int k = 0; meshOffsets.clear(); + meshOffsets.reserve(r.meshes.Size() + 1); + mVertexRemappingTables.clear(); + + // Count the number of aiMeshes + unsigned int num_aiMeshes = 0; + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + meshOffsets.push_back(num_aiMeshes); + num_aiMeshes += unsigned(r.meshes[m].primitives.size()); + } + meshOffsets.push_back(num_aiMeshes); // add a last element so we can always do meshOffsets[n+1] - meshOffsets[n] + + std::vector reverseMappingIndices; + std::vector indexBuffer; + meshes.reserve(num_aiMeshes); + mVertexRemappingTables.resize(num_aiMeshes); for (unsigned int m = 0; m < r.meshes.Size(); ++m) { Mesh &mesh = r.meshes[m]; - meshOffsets.push_back(k); - k += unsigned(mesh.primitives.size()); - for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { Mesh::Primitive &prim = mesh.primitives[p]; + Mesh::Primitive::Attributes &attr = prim.attributes; + + // Find out the maximum number of vertices: + size_t numAllVertices = 0; + if (!attr.position.empty() && attr.position[0]) { + numAllVertices = attr.position[0]->count; + } + + // Extract used vertices: + bool useIndexBuffer = prim.indices; + std::vector *vertexRemappingTable = nullptr; + + if (useIndexBuffer) { + size_t count = prim.indices->count; + indexBuffer.resize(count); + reverseMappingIndices.clear(); + vertexRemappingTable = &mVertexRemappingTables[meshes.size()]; + vertexRemappingTable->reserve(count / 3); // this is a very rough heuristic to reduce re-allocations + Accessor::Indexer data = prim.indices->GetIndexer(); + if (!data.IsValid()) { + throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name)); + } + + // Build the vertex remapping table and the modified index buffer (used later instead of the original one) + // In case no index buffer is used, the original vertex arrays are being used so no remapping is required in the first place. + const unsigned int unusedIndex = ~0u; + for (unsigned int i = 0; i < count; ++i) { + unsigned int index = data.GetUInt(i); + if (index >= numAllVertices) { + // Out-of-range indices will be filtered out when adding the faces and then lead to a warning. At this stage, we just keep them. + indexBuffer[i] = index; + continue; + } + if (index >= reverseMappingIndices.size()) { + reverseMappingIndices.resize(index + 1, unusedIndex); + } + if (reverseMappingIndices[index] == unusedIndex) { + reverseMappingIndices[index] = static_cast(vertexRemappingTable->size()); + vertexRemappingTable->push_back(index); + } + indexBuffer[i] = reverseMappingIndices[index]; + } + } + aiMesh *aim = new aiMesh(); meshes.push_back(std::unique_ptr(aim)); @@ -467,7 +539,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { if (mesh.primitives.size() > 1) { ai_uint32 &len = aim->mName.length; aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(AI_MAXLEN - len - 1), p); } switch (prim.mode) { @@ -488,28 +560,25 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { break; } - Mesh::Primitive::Attributes &attr = prim.attributes; - if (!attr.position.empty() && attr.position[0]) { - aim->mNumVertices = static_cast(attr.position[0]->count); - attr.position[0]->ExtractData(aim->mVertices); + aim->mNumVertices = static_cast(attr.position[0]->ExtractData(aim->mVertices, vertexRemappingTable)); } if (!attr.normal.empty() && attr.normal[0]) { - if (attr.normal[0]->count != aim->mNumVertices) { + if (attr.normal[0]->count != numAllVertices) { DefaultLogger::get()->warn("Normal count in mesh \"", mesh.name, "\" does not match the vertex count, normals ignored."); } else { - attr.normal[0]->ExtractData(aim->mNormals); + attr.normal[0]->ExtractData(aim->mNormals, vertexRemappingTable); // only extract tangents if normals are present if (!attr.tangent.empty() && attr.tangent[0]) { - if (attr.tangent[0]->count != aim->mNumVertices) { + if (attr.tangent[0]->count != numAllVertices) { DefaultLogger::get()->warn("Tangent count in mesh \"", mesh.name, "\" does not match the vertex count, tangents ignored."); } else { // generate bitangents from normals and tangents according to spec Tangent *tangents = nullptr; - attr.tangent[0]->ExtractData(tangents); + attr.tangent[0]->ExtractData(tangents, vertexRemappingTable); aim->mTangents = new aiVector3D[aim->mNumVertices]; aim->mBitangents = new aiVector3D[aim->mNumVertices]; @@ -526,7 +595,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { - if (attr.color[c]->count != aim->mNumVertices) { + if (attr.color[c]->count != numAllVertices) { DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name, "\" does not match the vertex count"); continue; @@ -534,12 +603,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { auto componentType = attr.color[c]->componentType; if (componentType == glTF2::ComponentType_FLOAT) { - attr.color[c]->ExtractData(aim->mColors[c]); + attr.color[c]->ExtractData(aim->mColors[c], vertexRemappingTable); } else { if (componentType == glTF2::ComponentType_UNSIGNED_BYTE) { - aim->mColors[c] = GetVertexColorsForType(attr.color[c]); + aim->mColors[c] = GetVertexColorsForType(attr.color[c], vertexRemappingTable); } else if (componentType == glTF2::ComponentType_UNSIGNED_SHORT) { - aim->mColors[c] = GetVertexColorsForType(attr.color[c]); + aim->mColors[c] = GetVertexColorsForType(attr.color[c], vertexRemappingTable); } } } @@ -549,13 +618,13 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { continue; } - if (attr.texcoord[tc]->count != aim->mNumVertices) { + if (attr.texcoord[tc]->count != numAllVertices) { DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name, "\" does not match the vertex count"); continue; } - attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc], vertexRemappingTable); aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); aiVector3D *values = aim->mTextureCoords[tc]; @@ -580,11 +649,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { Mesh::Primitive::Target &target = targets[i]; if (needPositions) { - if (target.position[0]->count != aim->mNumVertices) { + if (target.position[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { aiVector3D *positionDiff = nullptr; - target.position[0]->ExtractData(positionDiff); + target.position[0]->ExtractData(positionDiff, vertexRemappingTable); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; } @@ -592,11 +661,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } } if (needNormals) { - if (target.normal[0]->count != aim->mNumVertices) { + if (target.normal[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { aiVector3D *normalDiff = nullptr; - target.normal[0]->ExtractData(normalDiff); + target.normal[0]->ExtractData(normalDiff, vertexRemappingTable); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; } @@ -607,14 +676,14 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { if (!aiAnimMesh.HasNormals()) { // prevent nullptr access to aiAnimMesh.mNormals below when no normals are available ASSIMP_LOG_WARN("Bitangents of target ", i, " in mesh \"", mesh.name, "\" can't be computed, because mesh has no normals."); - } else if (target.tangent[0]->count != aim->mNumVertices) { + } else if (target.tangent[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { Tangent *tangent = nullptr; - attr.tangent[0]->ExtractData(tangent); + attr.tangent[0]->ExtractData(tangent, vertexRemappingTable); aiVector3D *tangentDiff = nullptr; - target.tangent[0]->ExtractData(tangentDiff); + target.tangent[0]->ExtractData(tangentDiff, vertexRemappingTable); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { tangent[vertexId].xyz += tangentDiff[vertexId]; @@ -638,20 +707,15 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { aiFace *facePtr = nullptr; size_t nFaces = 0; - if (prim.indices) { - size_t count = prim.indices->count; - - Accessor::Indexer data = prim.indices->GetIndexer(); - if (!data.IsValid()) { - throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name)); - } + if (useIndexBuffer) { + size_t count = indexBuffer.size(); switch (prim.mode) { case PrimitiveMode_POINTS: { nFaces = count; facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; ++i) { - SetFaceAndAdvance1(facePtr, aim->mNumVertices, data.GetUInt(i)); + SetFaceAndAdvance1(facePtr, aim->mNumVertices, indexBuffer[i]); } break; } @@ -664,7 +728,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 2) { - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1]); } break; } @@ -673,12 +737,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { case PrimitiveMode_LINE_STRIP: { nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); facePtr = faces = new aiFace[nFaces]; - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1]); for (unsigned int i = 2; i < count; ++i) { - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i - 1), data.GetUInt(i)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i - 1], indexBuffer[i]); } if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(static_cast(count) - 1), faces[0].mIndices[0]); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[static_cast(count) - 1], faces[0].mIndices[0]); } break; } @@ -691,7 +755,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 3) { - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]); } break; } @@ -702,10 +766,10 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { // The ordering is to ensure that the triangles are all drawn with the same orientation if ((i + 1) % 2 == 0) { // For even n, vertices n + 1, n, and n + 2 define triangle n - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i + 1], indexBuffer[i], indexBuffer[i + 2]); } else { // For odd n, vertices n, n+1, and n+2 define triangle n - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]); } } break; @@ -713,9 +777,9 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { case PrimitiveMode_TRIANGLE_FAN: nFaces = count - 2; facePtr = faces = new aiFace[nFaces]; - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1], indexBuffer[2]); for (unsigned int i = 1; i < nFaces; ++i) { - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[i + 1], indexBuffer[i + 2]); } break; } @@ -820,8 +884,6 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } } - meshOffsets.push_back(k); - CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); } @@ -846,7 +908,7 @@ void glTF2Importer::ImportCameras(glTF2::Asset &r) { if (cam.type == Camera::Perspective) { aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; - aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mHorizontalFOV = 2.0f * std::atan(std::tan(cam.cameraProperties.perspective.yfov * 0.5f) * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect)); aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; } else { @@ -954,7 +1016,8 @@ static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) { } } -static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector> &map) { +static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector> &map, std::vector* vertexRemappingTablePtr) { + Mesh::Primitive::Attributes &attr = primitive.attributes; if (attr.weight.empty() || attr.joint.empty()) { return; @@ -963,14 +1026,14 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vectorcount; + size_t num_vertices = 0; struct Weights { float values[4]; }; Weights **weights = new Weights*[attr.weight.size()]; for (size_t w = 0; w < attr.weight.size(); ++w) { - attr.weight[w]->ExtractData(weights[w]); + num_vertices = attr.weight[w]->ExtractData(weights[w], vertexRemappingTablePtr); } struct Indices8 { @@ -984,12 +1047,12 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vectorGetElementSize() == 4) { indices8 = new Indices8*[attr.joint.size()]; for (size_t j = 0; j < attr.joint.size(); ++j) { - attr.joint[j]->ExtractData(indices8[j]); + attr.joint[j]->ExtractData(indices8[j], vertexRemappingTablePtr); } } else { indices16 = new Indices16 *[attr.joint.size()]; for (size_t j = 0; j < attr.joint.size(); ++j) { - attr.joint[j]->ExtractData(indices16[j]); + attr.joint[j]->ExtractData(indices16[j], vertexRemappingTablePtr); } } // @@ -1048,15 +1111,13 @@ void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) { } } -void ParseExtras(aiMetadata *metadata, const CustomExtension &extension) { - if (extension.mValues.isPresent) { - for (auto const &subExtension : extension.mValues.value) { - ParseExtensions(metadata, subExtension); - } +void ParseExtras(aiMetadata* metadata, const Extras& extras) { + for (auto const &value : extras.mValues) { + ParseExtensions(metadata, value); } } -aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { +aiNode *glTF2Importer::ImportNode(glTF2::Asset &r, glTF2::Ref &ptr) { Node &node = *ptr; aiNode *ainode = new aiNode(GetNodeName(node)); @@ -1068,18 +1129,18 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & std::fill(ainode->mChildren, ainode->mChildren + ainode->mNumChildren, nullptr); for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { - aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + aiNode *child = ImportNode(r, node.children[i]); child->mParent = ainode; ainode->mChildren[i] = child; } } - if (node.customExtensions || node.extras) { + if (node.customExtensions || node.extras.HasExtras()) { ainode->mMetaData = new aiMetadata; if (node.customExtensions) { ParseExtensions(ainode->mMetaData, node.customExtensions); } - if (node.extras) { + if (node.extras.HasExtras()) { ParseExtras(ainode->mMetaData, node.extras); } } @@ -1101,11 +1162,13 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & if (node.skin) { for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { - aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo]; + unsigned int aiMeshIdx = meshOffsets[mesh_idx] + primitiveNo; + aiMesh *mesh = mScene->mMeshes[aiMeshIdx]; unsigned int numBones = static_cast(node.skin->jointNames.size()); + std::vector *vertexRemappingTablePtr = mVertexRemappingTables[aiMeshIdx].empty() ? nullptr : &mVertexRemappingTables[aiMeshIdx]; std::vector> weighting(numBones); - BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); + BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting, vertexRemappingTablePtr); mesh->mNumBones = static_cast(numBones); mesh->mBones = new aiBone *[mesh->mNumBones]; @@ -1122,7 +1185,7 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & // mapping which makes things doubly-slow. mat4 *pbindMatrices = nullptr; - node.skin->inverseBindMatrices->ExtractData(pbindMatrices); + node.skin->inverseBindMatrices->ExtractData(pbindMatrices, nullptr); for (uint32_t i = 0; i < numBones; ++i) { const std::vector &weights = weighting[i]; @@ -1168,16 +1231,11 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & } if (node.camera) { - pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; - if (node.translation.isPresent) { - aiVector3D trans; - CopyValue(node.translation.value, trans); - pScene->mCameras[node.camera.GetIndex()]->mPosition = trans; - } + mScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; } if (node.light) { - pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + mScene->mLights[node.light.GetIndex()]->mName = ainode->mName; // range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual // it is added to meta data of parent node, because there is no other place to put it @@ -1209,7 +1267,7 @@ void glTF2Importer::ImportNodes(glTF2::Asset &r) { // The root nodes unsigned int numRootNodes = unsigned(rootNodes.size()); if (numRootNodes == 1) { // a single root node: use it - mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + mScene->mRootNode = ImportNode(r, rootNodes[0]); } else if (numRootNodes > 1) { // more than one root node: create a fake root aiNode *root = mScene->mRootNode = new aiNode("ROOT"); @@ -1217,7 +1275,7 @@ void glTF2Importer::ImportNodes(glTF2::Asset &r) { std::fill(root->mChildren, root->mChildren + numRootNodes, nullptr); for (unsigned int i = 0; i < numRootNodes; ++i) { - aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + aiNode *node = ImportNode(r, rootNodes[i]); node->mParent = root; root->mChildren[root->mNumChildren++] = node; } @@ -1572,7 +1630,7 @@ void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { if (!img.mimeType.empty()) { const char *ext = strchr(img.mimeType.c_str(), '/') + 1; if (ext) { - if (strcmp(ext, "jpeg") == 0) { + if (strncmp(ext, "jpeg", 4) == 0) { ext = "jpg"; } else if (strcmp(ext, "ktx2") == 0) { // basisu: ktx remains ext = "kx2"; @@ -1580,9 +1638,9 @@ void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { ext = "bu"; } - size_t len = strlen(ext); + const size_t len = strlen(ext); if (len <= 3) { - strcpy(tex->achFormatHint, ext); + strncpy(tex->achFormatHint, ext, len); } } } @@ -1618,13 +1676,17 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO // clean all member arrays meshOffsets.clear(); + mVertexRemappingTables.clear(); mEmbeddedTexIdxs.clear(); this->mScene = pScene; // read the asset file glTF2::Asset asset(pIOHandler, static_cast(mSchemaDocumentProvider)); - asset.Load(pFile, GetExtension(pFile) == "glb"); + asset.Load(pFile, + CheckMagicToken( + pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0, + static_cast(strlen(AI_GLB_MAGIC_NUMBER)))); if (asset.scene) { pScene->mName = asset.scene->name; } diff --git a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.h b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.h index 831bcd7d2..68af0cb9c 100644 --- a/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.h +++ b/Engine/lib/assimp/code/AssetLib/glTF2/glTF2Importer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_GLTF2IMPORTER_H_INC #include +#include struct aiNode; @@ -59,13 +60,13 @@ namespace Assimp { class glTF2Importer : public BaseImporter { public: glTF2Importer(); - ~glTF2Importer() override; + ~glTF2Importer() override = default; bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; protected: const aiImporterDesc *GetInfo() const override; void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; - virtual void SetupProperties(const Importer *pImp) override; + void SetupProperties(const Importer *pImp) override; private: void ImportEmbeddedTextures(glTF2::Asset &a); @@ -76,10 +77,12 @@ private: void ImportNodes(glTF2::Asset &a); void ImportAnimations(glTF2::Asset &a); void ImportCommonMetadata(glTF2::Asset &a); + aiNode *ImportNode(glTF2::Asset &r, glTF2::Ref &ptr); private: std::vector meshOffsets; std::vector mEmbeddedTexIdxs; + std::vector> mVertexRemappingTables; // for each converted aiMesh in the scene, it stores a list of vertices that are actually used aiScene *mScene; /// An instance of rapidjson::IRemoteSchemaDocumentProvider diff --git a/Engine/lib/assimp/code/CApi/AssimpCExport.cpp b/Engine/lib/assimp/code/CApi/AssimpCExport.cpp index 5e43958d0..99ad41ab7 100644 --- a/Engine/lib/assimp/code/CApi/AssimpCExport.cpp +++ b/Engine/lib/assimp/code/CApi/AssimpCExport.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.cpp b/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.cpp index 579545ecc..84648482b 100644 --- a/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.cpp +++ b/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -47,14 +45,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { +// ------------------------------------------------------------------------------------------------ CIOStreamWrapper::~CIOStreamWrapper() { - /* Various places depend on this destructor to close the file */ - if (mFile) { + // Various places depend on this destructor to close the file + if (mFile != nullptr) { + mIO->mFileSystem->CloseProc(mIO->mFileSystem, mFile); } } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Read(void *pvBuffer, size_t pSize, size_t pCount) { @@ -62,7 +62,7 @@ size_t CIOStreamWrapper::Read(void *pvBuffer, return mFile->ReadProc(mFile, (char *)pvBuffer, pSize, pCount); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Write(const void *pvBuffer, size_t pSize, size_t pCount) { @@ -70,23 +70,23 @@ size_t CIOStreamWrapper::Write(const void *pvBuffer, return mFile->WriteProc(mFile, (const char *)pvBuffer, pSize, pCount); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ aiReturn CIOStreamWrapper::Seek(size_t pOffset, aiOrigin pOrigin) { return mFile->SeekProc(mFile, pOffset, pOrigin); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Tell() const { return mFile->TellProc(mFile); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::FileSize() const { return mFile->FileSizeProc(mFile); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ void CIOStreamWrapper::Flush() { return mFile->FlushProc(mFile); } diff --git a/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.h b/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.h index 768be3746..34c4a7311 100644 --- a/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.h +++ b/Engine/lib/assimp/code/CApi/CInterfaceIOWrapper.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -47,48 +47,59 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { class CIOSystemWrapper; // ------------------------------------------------------------------------------------------------ -// Custom IOStream implementation for the C-API -class CIOStreamWrapper : public IOStream { +/// @brief Custom IOStream implementation for the C-API- +// ------------------------------------------------------------------------------------------------ +class CIOStreamWrapper final : public IOStream { public: - explicit CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io) : - mFile(pFile), - mIO(io) {} - ~CIOStreamWrapper(void); - - size_t Read(void *pvBuffer, size_t pSize, size_t pCount); - size_t Write(const void *pvBuffer, size_t pSize, size_t pCount); - aiReturn Seek(size_t pOffset, aiOrigin pOrigin); - size_t Tell(void) const; - size_t FileSize() const; - void Flush(); + explicit CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io); + ~CIOStreamWrapper() override; + size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override; + size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override; + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; + size_t Tell(void) const override; + size_t FileSize() const override; + void Flush() override; private: aiFile *mFile; CIOSystemWrapper *mIO; }; -class CIOSystemWrapper : public IOSystem { +inline CIOStreamWrapper::CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io) : + mFile(pFile), + mIO(io) { + ai_assert(io != nullptr); +} + +// ------------------------------------------------------------------------------------------------ +/// @brief Custom IO-System wrapper implementation for the C-API. +// ------------------------------------------------------------------------------------------------ +class CIOSystemWrapper final : public IOSystem { friend class CIOStreamWrapper; public: - explicit CIOSystemWrapper(aiFileIO *pFile) : - mFileSystem(pFile) {} - - bool Exists(const char *pFile) const; - char getOsSeparator() const; - IOStream *Open(const char *pFile, const char *pMode = "rb"); - void Close(IOStream *pFile); + explicit CIOSystemWrapper(aiFileIO *pFile); + ~CIOSystemWrapper() override = default; + bool Exists(const char *pFile) const override; + char getOsSeparator() const override; + IOStream *Open(const char *pFile, const char *pMode = "rb") override; + void Close(IOStream *pFile) override; private: aiFileIO *mFileSystem; }; +inline CIOSystemWrapper::CIOSystemWrapper(aiFileIO *pFile) : mFileSystem(pFile) { + ai_assert(pFile != nullptr); +} + } // namespace Assimp -#endif +#endif // AI_CIOSYSTEM_H_INCLUDED diff --git a/Engine/lib/assimp/code/CMakeLists.txt b/Engine/lib/assimp/code/CMakeLists.txt index b87bc665f..304235d80 100644 --- a/Engine/lib/assimp/code/CMakeLists.txt +++ b/Engine/lib/assimp/code/CMakeLists.txt @@ -1,7 +1,6 @@ # Open Asset Import Library (assimp) # ---------------------------------------------------------------------- -# -# Copyright (c) 2006-2022, assimp team +# Copyright (c) 2006-2024, assimp team # # All rights reserved. # @@ -43,7 +42,7 @@ # 3) Add libassimp using the file lists (eliminates duplication of file names between # source groups and library command) # -cmake_minimum_required( VERSION 3.10 ) +cmake_minimum_required( VERSION 3.22 ) SET( HEADER_PATH ../include/assimp ) if(NOT ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) @@ -94,6 +93,7 @@ SET( PUBLIC_HEADERS ${HEADER_PATH}/vector3.inl ${HEADER_PATH}/version.h ${HEADER_PATH}/cimport.h + ${HEADER_PATH}/AssertHandler.h ${HEADER_PATH}/importerdesc.h ${HEADER_PATH}/Importer.hpp ${HEADER_PATH}/DefaultLogger.hpp @@ -194,6 +194,8 @@ SET( Common_SRCS Common/ScenePreprocessor.cpp Common/ScenePreprocessor.h Common/SkeletonMeshBuilder.cpp + Common/StackAllocator.h + Common/StackAllocator.inl Common/StandardShapes.cpp Common/TargetAnimation.cpp Common/TargetAnimation.h @@ -218,6 +220,12 @@ SET( CApi_SRCS ) SOURCE_GROUP(CApi FILES ${CApi_SRCS}) +SET(Geometry_SRCS + Geometry/GeometryUtils.h + Geometry/GeometryUtils.cpp +) +SOURCE_GROUP(Geometry FILES ${Geometry_SRCS}) + SET( STEPParser_SRCS AssetLib/STEPParser/STEPFileReader.h AssetLib/STEPParser/STEPFileReader.cpp @@ -263,15 +271,14 @@ MACRO(ADD_ASSIMP_IMPORTER name) ENDMACRO() if (NOT ASSIMP_NO_EXPORT) - # if this variable is set to TRUE, the user can manually disable exporters by setting # ASSIMP_BUILD_XXX_EXPORTER to FALSE for each exporter # if this variable is set to FALSE, the user can manually enable exporters by setting # ASSIMP_BUILD_XXX_EXPORTER to TRUE for each exporter OPTION(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" TRUE) - # macro to add the CMake Option ADD_ASSIMP_IMPORTER_ which enables compile of loader - # this way selective loaders can be compiled (reduces filesize + compile time) + # macro to add the CMake Option ADD_ASSIMP_EXPORTER_ which enables compilation of an exporter + # this way selective exporters can be compiled (reduces filesize + compile time) MACRO(ADD_ASSIMP_EXPORTER name) IF (ASSIMP_NO_EXPORT) set(ASSIMP_EXPORTER_ENABLED FALSE) @@ -409,14 +416,12 @@ ADD_ASSIMP_IMPORTER( LWS AssetLib/LWS/LWSLoader.h ) -ADD_ASSIMP_IMPORTER( M3D - AssetLib/M3D/M3DMaterials.h - AssetLib/M3D/M3DImporter.h - AssetLib/M3D/M3DImporter.cpp - AssetLib/M3D/M3DWrapper.h - AssetLib/M3D/M3DWrapper.cpp - AssetLib/M3D/m3d.h -) +if (ASSIMP_BUILD_M3D_IMPORTER) + ADD_ASSIMP_IMPORTER( M3D + AssetLib/M3D/M3DImporter.cpp + AssetLib/M3D/M3DWrapper.cpp + ) +endif () # if (ASSIMP_BUILD_M3D_IMPORTER) ADD_ASSIMP_IMPORTER( MD2 AssetLib/MD2/MD2FileData.h @@ -463,6 +468,14 @@ ADD_ASSIMP_IMPORTER( MDL AssetLib/MDL/HalfLife/UniqueNameGenerator.h ) +IF(((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU) AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13) + message(STATUS "GCC13 detected disabling \"-Warray-bounds and -Wstringop-overflow\" for" + " AssetLib/MDL/MDLLoader.cpp as it appears to be a false positive") + set_source_files_properties(AssetLib/MDL/MDLLoader.cpp PROPERTIES + COMPILE_FLAGS "-Wno-array-bounds -Wno-stringop-overflow" + ) +endif() + SET( MaterialSystem_SRCS Material/MaterialSystem.cpp Material/MaterialSystem.h @@ -495,6 +508,14 @@ ADD_ASSIMP_IMPORTER( OBJ AssetLib/Obj/ObjTools.h ) +IF(((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU) AND CMAKE_CXX_COMPILER_VERSION EQUAL 14) + message(STATUS "GCC14 detected disabling \"-Wmaybe-uninitialized\" for" + " AssetLib/Obj/ObjFileParser.cpp as it appears to be a false positive") + set_source_files_properties(AssetLib/Obj/ObjFileParser.cpp PROPERTIES + COMPILE_FLAGS "-Wno-maybe-uninitialized" + ) +endif() + ADD_ASSIMP_IMPORTER( OGRE AssetLib/Ogre/OgreImporter.h AssetLib/Ogre/OgreStructs.h @@ -640,9 +661,10 @@ if (NOT ASSIMP_NO_EXPORT) AssetLib/Assxml/AssxmlFileWriter.h AssetLib/Assxml/AssxmlFileWriter.cpp) - ADD_ASSIMP_EXPORTER(M3D - AssetLib/M3D/M3DExporter.h - AssetLib/M3D/M3DExporter.cpp) + if ((NOT ASSIMP_NO_EXPORT) AND ASSIMP_BUILD_M3D_EXPORTER) + ADD_ASSIMP_EXPORTER(M3D + AssetLib/M3D/M3DExporter.cpp) + endif () # if (ASSIMP_BUILD_M3D_EXPORTER) ADD_ASSIMP_EXPORTER(COLLADA AssetLib/Collada/ColladaExporter.h @@ -804,6 +826,17 @@ ADD_ASSIMP_IMPORTER( 3D AssetLib/Unreal/UnrealLoader.h ) +ADD_ASSIMP_IMPORTER( USD + AssetLib/USD/USDLoader.cpp + AssetLib/USD/USDLoader.h + AssetLib/USD/USDLoaderImplTinyusdz.cpp + AssetLib/USD/USDLoaderImplTinyusdz.h + AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp + AssetLib/USD/USDLoaderImplTinyusdzHelper.h + AssetLib/USD/USDLoaderUtil.cpp + AssetLib/USD/USDLoaderUtil.h +) + ADD_ASSIMP_IMPORTER( X AssetLib/X/XFileHelper.h AssetLib/X/XFileImporter.cpp @@ -897,11 +930,128 @@ SET( Extra_SRCS ) SOURCE_GROUP( Extra FILES ${Extra_SRCS}) +# USD/USDA/USDC/USDZ support +# tinyusdz +IF (ASSIMP_BUILD_USD_IMPORTER) + if (ASSIMP_BUILD_USD_VERBOSE_LOGS) + ADD_DEFINITIONS( -DASSIMP_USD_VERBOSE_LOGS) + endif () + # Use CMAKE_CURRENT_SOURCE_DIR which provides assimp-local path (CMAKE_SOURCE_DIR is + # relative to top-level/main project) + set(Tinyusdz_BASE_ABSPATH "${CMAKE_CURRENT_SOURCE_DIR}/../contrib/tinyusdz") + set(Tinyusdz_REPO_ABSPATH "${Tinyusdz_BASE_ABSPATH}/autoclone") + + # Note: ALWAYS specify a git commit hash (or tag) instead of a branch name; using a branch + # name can lead to non-deterministic (unpredictable) results since the code is potentially + # in flux + # "dev" branch, 28 Oct 2024 + set(TINYUSDZ_GIT_TAG "36f2aabb256b360365989c01a52f839a57dfe2a6") + message("****") + message("\n\n**** Cloning tinyusdz repo, git tag ${TINYUSDZ_GIT_TAG}\n\n") + + # Patch required to build arm32 on android + set(TINYUSDZ_PATCH_CMD git apply ${Tinyusdz_BASE_ABSPATH}/patches/tinyusdz.patch) + + # Note: CMake's "FetchContent" (which executes at configure time) is much better for this use case + # than "ExternalProject" (which executes at build time); we just want to clone a repo and + # block (wait) as long as necessary until cloning is complete, so we immediately have full + # access to the cloned source files + include(FetchContent) + # Only want to clone once (on Android, using SOURCE_DIR will clone per-ABI (x86, x86_64 etc)) + set(FETCHCONTENT_BASE_DIR ${Tinyusdz_REPO_ABSPATH}) + set(FETCHCONTENT_QUIET on) # Turn off to troubleshoot repo clone problems + set(FETCHCONTENT_UPDATES_DISCONNECTED on) # Prevent other ABIs from re-cloning/re-patching etc + FetchContent_Declare( + tinyusdz_repo + GIT_REPOSITORY "https://github.com/lighttransport/tinyusdz" + GIT_TAG ${TINYUSDZ_GIT_TAG} + PATCH_COMMAND ${TINYUSDZ_PATCH_CMD} + ) + FetchContent_MakeAvailable(tinyusdz_repo) + message("**** Finished cloning tinyusdz repo") + message("****") + + set(Tinyusdz_SRC_ABSPATH "${Tinyusdz_REPO_ABSPATH}/tinyusdz_repo-src/src") + set(Tinyusdz_SRCS + ${Tinyusdz_SRC_ABSPATH}/ascii-parser.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-basetype.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-timesamples.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-timesamples-array.cc + ${Tinyusdz_SRC_ABSPATH}/asset-resolution.cc + ${Tinyusdz_SRC_ABSPATH}/composition.cc + ${Tinyusdz_SRC_ABSPATH}/crate-format.cc + ${Tinyusdz_SRC_ABSPATH}/crate-pprint.cc + ${Tinyusdz_SRC_ABSPATH}/crate-reader.cc + ${Tinyusdz_SRC_ABSPATH}/image-loader.cc + ${Tinyusdz_SRC_ABSPATH}/image-util.cc + ${Tinyusdz_SRC_ABSPATH}/image-writer.cc + ${Tinyusdz_SRC_ABSPATH}/io-util.cc + ${Tinyusdz_SRC_ABSPATH}/linear-algebra.cc + ${Tinyusdz_SRC_ABSPATH}/path-util.cc + ${Tinyusdz_SRC_ABSPATH}/pprinter.cc + ${Tinyusdz_SRC_ABSPATH}/prim-composition.cc + ${Tinyusdz_SRC_ABSPATH}/prim-reconstruct.cc + ${Tinyusdz_SRC_ABSPATH}/prim-types.cc + ${Tinyusdz_SRC_ABSPATH}/primvar.cc + ${Tinyusdz_SRC_ABSPATH}/stage.cc + ${Tinyusdz_SRC_ABSPATH}/str-util.cc + ${Tinyusdz_SRC_ABSPATH}/tiny-format.cc + ${Tinyusdz_SRC_ABSPATH}/tinyusdz.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-animatable.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-animatable-fallback.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-fallback.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/facial.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/prim-apply.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/render-data.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/scene-access.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/shader-network.cc + ${Tinyusdz_SRC_ABSPATH}/usda-reader.cc + ${Tinyusdz_SRC_ABSPATH}/usda-writer.cc + ${Tinyusdz_SRC_ABSPATH}/usdc-reader.cc + ${Tinyusdz_SRC_ABSPATH}/usdc-writer.cc + ${Tinyusdz_SRC_ABSPATH}/usdGeom.cc + ${Tinyusdz_SRC_ABSPATH}/usdLux.cc + ${Tinyusdz_SRC_ABSPATH}/usdMtlx.cc + ${Tinyusdz_SRC_ABSPATH}/usdShade.cc + ${Tinyusdz_SRC_ABSPATH}/usdSkel.cc + ${Tinyusdz_SRC_ABSPATH}/value-pprint.cc + ${Tinyusdz_SRC_ABSPATH}/value-types.cc + ${Tinyusdz_SRC_ABSPATH}/xform.cc + ) + + set(Tinyusdz_DEP_SOURCES + ${Tinyusdz_SRC_ABSPATH}/external/fpng.cpp + #${Tinyusdz_SRC_ABSPATH}/external/staticstruct.cc + #${Tinyusdz_SRC_ABSPATH}/external/string_id/database.cpp + #${Tinyusdz_SRC_ABSPATH}/external/string_id/error.cpp + #${Tinyusdz_SRC_ABSPATH}/external/string_id/string_id.cpp + #${Tinyusdz_SRC_ABSPATH}/external/tinyxml2/tinyxml2.cpp + ${Tinyusdz_SRC_ABSPATH}/integerCoding.cpp + ${Tinyusdz_SRC_ABSPATH}/lz4-compression.cc + ${Tinyusdz_SRC_ABSPATH}/lz4/lz4.c + ) + + set(tinyusdz_INCLUDE_DIRS "${Tinyusdz_SRC_ABSPATH}") + INCLUDE_DIRECTORIES(${tinyusdz_INCLUDE_DIRS}) + SOURCE_GROUP( Contrib\\Tinyusdz + FILES + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} + ) + MESSAGE(STATUS "tinyusdz enabled") +ELSE() # IF (ASSIMP_BUILD_USD_IMPORTER) + set(Tinyusdz_SRCS "") + set(Tinyusdz_DEP_SOURCES "") + MESSAGE(STATUS "tinyusdz disabled") +ENDIF() # IF (ASSIMP_BUILD_USD_IMPORTER) + # pugixml IF(ASSIMP_HUNTER_ENABLED) hunter_add_package(pugixml) find_package(pugixml CONFIG REQUIRED) -ELSE() +ELSEIF(NOT TARGET pugixml::pugixml) SET( Pugixml_SRCS ../contrib/pugixml/src/pugiconfig.hpp ../contrib/pugixml/src/pugixml.hpp @@ -915,26 +1065,26 @@ IF(ASSIMP_HUNTER_ENABLED) hunter_add_package(utf8) find_package(utf8cpp CONFIG REQUIRED) ELSE() - # utf8 is header-only, so Assimp doesn't need to do anything. + INCLUDE_DIRECTORIES("../contrib/utf8cpp/source") ENDIF() # polyclipping -IF(ASSIMP_HUNTER_ENABLED) - hunter_add_package(polyclipping) - find_package(polyclipping CONFIG REQUIRED) -ELSE() +#IF(ASSIMP_HUNTER_ENABLED) +# hunter_add_package(polyclipping) +# find_package(polyclipping CONFIG REQUIRED) +#ELSE() SET( Clipper_SRCS ../contrib/clipper/clipper.hpp ../contrib/clipper/clipper.cpp ) SOURCE_GROUP( Contrib\\Clipper FILES ${Clipper_SRCS}) -ENDIF() +#ENDIF() # poly2tri -IF(ASSIMP_HUNTER_ENABLED) - hunter_add_package(poly2tri) - find_package(poly2tri CONFIG REQUIRED) -ELSE() +#IF(ASSIMP_HUNTER_ENABLED) +# hunter_add_package(poly2tri) +# find_package(poly2tri CONFIG REQUIRED) +#ELSE() SET( Poly2Tri_SRCS ../contrib/poly2tri/poly2tri/common/shapes.cc ../contrib/poly2tri/poly2tri/common/shapes.h @@ -949,7 +1099,7 @@ ELSE() ../contrib/poly2tri/poly2tri/sweep/sweep_context.h ) SOURCE_GROUP( Contrib\\Poly2Tri FILES ${Poly2Tri_SRCS}) -ENDIF() +#ENDIF() # minizip/unzip IF(ASSIMP_HUNTER_ENABLED) @@ -957,7 +1107,6 @@ IF(ASSIMP_HUNTER_ENABLED) find_package(minizip CONFIG REQUIRED) ELSE() SET( unzip_SRCS - ../contrib/unzip/crypt.c ../contrib/unzip/crypt.h ../contrib/unzip/ioapi.c ../contrib/unzip/ioapi.h @@ -1129,6 +1278,7 @@ SET( assimp_src ${Core_SRCS} ${CApi_SRCS} ${Common_SRCS} + ${Geometry_SRCS} ${Logging_SRCS} ${Exporter_SRCS} ${PostProcessing_SRCS} @@ -1147,6 +1297,8 @@ SET( assimp_src ${openddl_parser_SRCS} ${open3dgc_SRCS} ${ziplib_SRCS} + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} ${Pugixml_SRCS} ${stb_SRCS} # Necessary to show the headers in the project when using the VC++ generator: @@ -1180,15 +1332,74 @@ ADD_LIBRARY(assimp::assimp ALIAS assimp) IF (BUILD_SHARED_LIBS) TARGET_COMPILE_DEFINITIONS(assimp PRIVATE ASSIMP_BUILD_DLL_EXPORT) ELSE () - TARGET_COMPILE_DEFINITIONS(assimp PRIVATE OPENDDL_STATIC_LIBARY) + TARGET_COMPILE_DEFINITIONS(assimp PRIVATE OPENDDL_STATIC_LIBARY P2T_STATIC_EXPORTS) ENDIF () TARGET_USE_COMMON_OUTPUT_DIRECTORY(assimp) +add_compile_options( + "$<$:-O0;-g3;-ggdb>" +) + IF (ASSIMP_WARNINGS_AS_ERRORS) MESSAGE(STATUS "Treating all warnings as errors (for assimp library only)") IF (MSVC) - TARGET_COMPILE_OPTIONS(assimp PRIVATE /W4 /WX) + + IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl + TARGET_COMPILE_OPTIONS(assimp PRIVATE -Wall -Werror + -Wno-microsoft-enum-value + -Wno-switch-enum + -Wno-covered-switch-default + -Wno-reserved-identifier + -Wno-c++98-compat-pedantic + -Wno-c++98-compat + -Wno-documentation + -Wno-documentation-unknown-command + -Wno-deprecated-dynamic-exception-spec + -Wno-undef + -Wno-suggest-destructor-override + -Wno-suggest-override + -Wno-zero-as-null-pointer-constant + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-extra-semi-stmt + -Wno-missing-prototypes + -Wno-old-style-cast + -Wno-cast-align + -Wno-cast-qual + -Wno-float-equal + -Wno-implicit-int-float-conversion + -Wno-sign-conversion + -Wno-implicit-float-conversion + -Wno-implicit-int-conversion + -Wno-float-conversion + -Wno-double-promotion + -Wno-unused-macros + -Wno-disabled-macro-expansion + -Wno-shadow-field + -Wno-shadow + -Wno-language-extension-token + -Wno-header-hygiene + -Wno-tautological-value-range-compare + -Wno-tautological-type-limit-compare + -Wno-missing-variable-declarations + -Wno-extra-semi + -Wno-nonportable-system-include-path + -Wno-undefined-reinterpret-cast + -Wno-shift-sign-overflow + -Wno-deprecated + -Wno-format-nonliteral + -Wno-comma + -Wno-implicit-fallthrough + -Wno-unused-template + -Wno-undefined-func-template + -Wno-declaration-after-statement + -Wno-deprecated-declarations + -Wno-deprecated-non-prototype + ) + ELSE() + TARGET_COMPILE_OPTIONS(assimp PRIVATE /W4 /WX) + ENDIF() ELSE() TARGET_COMPILE_OPTIONS(assimp PRIVATE -Wall -Werror) ENDIF() @@ -1206,9 +1417,7 @@ TARGET_INCLUDE_DIRECTORIES ( assimp PUBLIC IF(ASSIMP_HUNTER_ENABLED) TARGET_LINK_LIBRARIES(assimp PUBLIC - polyclipping::polyclipping openddlparser::openddl_parser - poly2tri::poly2tri minizip::minizip ZLIB::zlib RapidJSON::rapidjson @@ -1221,13 +1430,16 @@ IF(ASSIMP_HUNTER_ENABLED) endif() if (ASSIMP_BUILD_DRACO) - target_link_libraries(assimp PUBLIC ${draco_LIBRARIES}) + target_link_libraries(assimp PRIVATE ${draco_LIBRARIES}) endif() ELSE() TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES}) if (ASSIMP_BUILD_DRACO) target_link_libraries(assimp ${draco_LIBRARIES}) endif() + if(TARGET pugixml::pugixml) + target_link_libraries(assimp pugixml::pugixml) + endif() ENDIF() if(ASSIMP_ANDROID_JNIIOSYSTEM) @@ -1319,7 +1531,12 @@ ENDIF() IF(NOT ASSIMP_HUNTER_ENABLED) if (UNZIP_FOUND) INCLUDE_DIRECTORIES(${UNZIP_INCLUDE_DIRS}) - TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES}) + # TODO if cmake required version has been updated to >3.12.0, collapse this to the second case only + if(${CMAKE_VERSION} VERSION_LESS "3.12.0") + TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES}) + else() + TARGET_LINK_LIBRARIES(assimp ${UNZIP_LINK_LIBRARIES}) + endif() else () INCLUDE_DIRECTORIES("../") endif () @@ -1327,22 +1544,23 @@ ENDIF() # Add RT-extension library for glTF importer with Open3DGC-compression. IF (RT_FOUND AND ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC) - TARGET_LINK_LIBRARIES(assimp ${RT_LIBRARY}) + TARGET_LINK_LIBRARIES(assimp rt) ENDIF () +IF(ASSIMP_INSTALL) + INSTALL( TARGETS assimp + EXPORT "${TARGETS_EXPORT_NAME}" + LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP-DEV_COMPONENT} + RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} + INCLUDES DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR} + ) + INSTALL( FILES ${PUBLIC_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp COMPONENT assimp-dev) + INSTALL( FILES ${COMPILER_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp/Compiler COMPONENT assimp-dev) +ENDIF() -INSTALL( TARGETS assimp - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} - ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP-DEV_COMPONENT} - RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} - FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR} COMPONENT ${LIBASSIMP_COMPONENT} - INCLUDES DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR} -) -INSTALL( FILES ${PUBLIC_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp COMPONENT assimp-dev) -INSTALL( FILES ${COMPILER_HEADERS} DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR}/assimp/Compiler COMPONENT assimp-dev) - -if (ASSIMP_ANDROID_JNIIOSYSTEM) +if (ASSIMP_ANDROID_JNIIOSYSTEM AND ASSIMP_INSTALL) INSTALL(FILES ${HEADER_PATH}/${ASSIMP_ANDROID_JNIIOSYSTEM_PATH}/AndroidJNIIOSystem.h DESTINATION ${ASSIMP_INCLUDE_INSTALL_DIR} COMPONENT assimp-dev) @@ -1357,25 +1575,29 @@ if(MSVC AND ASSIMP_INSTALL_PDB) COMPILE_PDB_NAME assimp${LIBRARY_SUFFIX} COMPILE_PDB_NAME_DEBUG assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX} ) - ENDIF() - IF(CMAKE_GENERATOR MATCHES "^Visual Studio") - install(FILES ${Assimp_BINARY_DIR}/code/Debug/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb - DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - CONFIGURATIONS Debug - ) - install(FILES ${Assimp_BINARY_DIR}/code/RelWithDebInfo/assimp${LIBRARY_SUFFIX}.pdb - DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - CONFIGURATIONS RelWithDebInfo - ) + IF(is_multi_config) + install(FILES ${Assimp_BINARY_DIR}/code/Debug/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS Debug + ) + install(FILES ${Assimp_BINARY_DIR}/code/RelWithDebInfo/assimp${LIBRARY_SUFFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS RelWithDebInfo + ) + ELSE() + install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS Debug + ) + install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}.pdb + DESTINATION ${ASSIMP_LIB_INSTALL_DIR} + CONFIGURATIONS RelWithDebInfo + ) + ENDIF() ELSE() - install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}${CMAKE_DEBUG_POSTFIX}.pdb + install(FILES $ DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - CONFIGURATIONS Debug - ) - install(FILES ${Assimp_BINARY_DIR}/code/assimp${LIBRARY_SUFFIX}.pdb - DESTINATION ${ASSIMP_LIB_INSTALL_DIR} - CONFIGURATIONS RelWithDebInfo ) ENDIF() ENDIF () diff --git a/Engine/lib/assimp/code/Common/AssertHandler.cpp b/Engine/lib/assimp/code/Common/AssertHandler.cpp index 469e7bec5..ee2d2b95c 100644 --- a/Engine/lib/assimp/code/Common/AssertHandler.cpp +++ b/Engine/lib/assimp/code/Common/AssertHandler.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,7 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of assert handling logic. */ -#include "AssertHandler.h" +#include #include #include diff --git a/Engine/lib/assimp/code/Common/Assimp.cpp b/Engine/lib/assimp/code/Common/Assimp.cpp index ee798238e..91896e405 100644 --- a/Engine/lib/assimp/code/Common/Assimp.cpp +++ b/Engine/lib/assimp/code/Common/Assimp.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -233,8 +233,13 @@ const aiScene *aiImportFileFromMemoryWithProperties( unsigned int pFlags, const char *pHint, const aiPropertyStore *props) { - ai_assert(nullptr != pBuffer); - ai_assert(0 != pLength); + if (pBuffer == nullptr) { + return nullptr; + } + + if (pLength == 0u) { + return nullptr; + } const aiScene *scene = nullptr; ASSIMP_BEGIN_EXCEPTION_REGION(); @@ -354,20 +359,25 @@ void CallbackToLogRedirector(const char *msg, char *dt) { s->write(msg); } +static LogStream *DefaultStream = nullptr; + // ------------------------------------------------------------------------------------------------ ASSIMP_API aiLogStream aiGetPredefinedLogStream(aiDefaultLogStream pStream, const char *file) { aiLogStream sout; ASSIMP_BEGIN_EXCEPTION_REGION(); - LogStream *stream = LogStream::createDefaultStream(pStream, file); - if (!stream) { + if (DefaultStream == nullptr) { + DefaultStream = LogStream::createDefaultStream(pStream, file); + } + + if (!DefaultStream) { sout.callback = nullptr; sout.user = nullptr; } else { sout.callback = &CallbackToLogRedirector; - sout.user = (char *)stream; + sout.user = (char *)DefaultStream; } - gPredefinedStreams.push_back(stream); + gPredefinedStreams.push_back(DefaultStream); ASSIMP_END_EXCEPTION_REGION(aiLogStream); return sout; } @@ -507,6 +517,11 @@ void aiGetMemoryRequirements(const C_STRUCT aiScene *pIn, ASSIMP_END_EXCEPTION_REGION(void); } +// ------------------------------------------------------------------------------------------------ +ASSIMP_API const C_STRUCT aiTexture *aiGetEmbeddedTexture(const C_STRUCT aiScene *pIn, const char *filename) { + return pIn->GetEmbeddedTexture(filename); +} + // ------------------------------------------------------------------------------------------------ ASSIMP_API aiPropertyStore *aiCreatePropertyStore(void) { return reinterpret_cast(new PropertyMap()); @@ -738,14 +753,14 @@ ASSIMP_API void aiVector2DivideByVector( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2Length( +ASSIMP_API ai_real aiVector2Length( const C_STRUCT aiVector2D *v) { ai_assert(nullptr != v); return v->Length(); } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2SquareLength( +ASSIMP_API ai_real aiVector2SquareLength( const C_STRUCT aiVector2D *v) { ai_assert(nullptr != v); return v->SquareLength(); @@ -759,7 +774,7 @@ ASSIMP_API void aiVector2Negate( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2DotProduct( +ASSIMP_API ai_real aiVector2DotProduct( const C_STRUCT aiVector2D *a, const C_STRUCT aiVector2D *b) { ai_assert(nullptr != a); @@ -854,14 +869,14 @@ ASSIMP_API void aiVector3DivideByVector( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3Length( +ASSIMP_API ai_real aiVector3Length( const C_STRUCT aiVector3D *v) { ai_assert(nullptr != v); return v->Length(); } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3SquareLength( +ASSIMP_API ai_real aiVector3SquareLength( const C_STRUCT aiVector3D *v) { ai_assert(nullptr != v); return v->SquareLength(); @@ -875,7 +890,7 @@ ASSIMP_API void aiVector3Negate( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3DotProduct( +ASSIMP_API ai_real aiVector3DotProduct( const C_STRUCT aiVector3D *a, const C_STRUCT aiVector3D *b) { ai_assert(nullptr != a); @@ -961,7 +976,7 @@ ASSIMP_API void aiMatrix3Inverse(C_STRUCT aiMatrix3x3 *mat) { } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiMatrix3Determinant(const C_STRUCT aiMatrix3x3 *mat) { +ASSIMP_API ai_real aiMatrix3Determinant(const C_STRUCT aiMatrix3x3 *mat) { ai_assert(nullptr != mat); return mat->Determinant(); } @@ -1061,7 +1076,7 @@ ASSIMP_API void aiMatrix4Inverse(C_STRUCT aiMatrix4x4 *mat) { } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiMatrix4Determinant(const C_STRUCT aiMatrix4x4 *mat) { +ASSIMP_API ai_real aiMatrix4Determinant(const C_STRUCT aiMatrix4x4 *mat) { ai_assert(nullptr != mat); return mat->Determinant(); } @@ -1131,7 +1146,7 @@ ASSIMP_API void aiMatrix4RotationX( ASSIMP_API void aiMatrix4RotationY( C_STRUCT aiMatrix4x4 *mat, const float angle) { - ai_assert(NULL != mat); + ai_assert(nullptr != mat); aiMatrix4x4::RotationY(angle, *mat); } @@ -1263,7 +1278,6 @@ ASSIMP_API void aiQuaternionInterpolate( aiQuaternion::Interpolate(*dst, *start, *end, factor); } - // stb_image is a lightweight image loader. It is shared by: // - M3D import // - PBRT export @@ -1273,20 +1287,22 @@ ASSIMP_API void aiQuaternionInterpolate( #define ASSIMP_HAS_PBRT_EXPORT (!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_PBRT_EXPORTER) #define ASSIMP_HAS_M3D ((!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER) +#ifndef STB_USE_HUNTER #if ASSIMP_HAS_PBRT_EXPORT -# define ASSIMP_NEEDS_STB_IMAGE 1 +#define ASSIMP_NEEDS_STB_IMAGE 1 #elif ASSIMP_HAS_M3D -# define ASSIMP_NEEDS_STB_IMAGE 1 -# define STBI_ONLY_PNG +#define ASSIMP_NEEDS_STB_IMAGE 1 +#define STBI_ONLY_PNG +#endif #endif // Ensure all symbols are linked correctly #if ASSIMP_NEEDS_STB_IMAGE - // Share stb_image's PNG loader with other importers/exporters instead of bringing our own copy. -# define STBI_ONLY_PNG -# ifdef ASSIMP_USE_STB_IMAGE_STATIC -# define STB_IMAGE_STATIC -# endif -# define STB_IMAGE_IMPLEMENTATION -# include "Common/StbCommon.h" +// Share stb_image's PNG loader with other importers/exporters instead of bringing our own copy. +#define STBI_ONLY_PNG +#ifdef ASSIMP_USE_STB_IMAGE_STATIC +#define STB_IMAGE_STATIC +#endif +#define STB_IMAGE_IMPLEMENTATION +#include "Common/StbCommon.h" #endif diff --git a/Engine/lib/assimp/code/Common/Base64.cpp b/Engine/lib/assimp/code/Common/Base64.cpp index 65308f4fd..76f9b120e 100644 --- a/Engine/lib/assimp/code/Common/Base64.cpp +++ b/Engine/lib/assimp/code/Common/Base64.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/BaseImporter.cpp b/Engine/lib/assimp/code/Common/BaseImporter.cpp index 587fa7bc1..5c70cc27e 100644 --- a/Engine/lib/assimp/code/Common/BaseImporter.cpp +++ b/Engine/lib/assimp/code/Common/BaseImporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,6 +59,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +namespace { +// Checks whether the passed string is a gcs version. +bool IsGcsVersion(const std::string &s) { + if (s.empty()) return false; + return std::all_of(s.cbegin(), s.cend(), [](const char c) { + // gcs only permits numeric characters. + return std::isdigit(static_cast(c)); + }); +} + +// Removes a possible version hash from a filename, as found for example in +// gcs uris (e.g. `gs://bucket/model.glb#1234`), see also +// https://github.com/GoogleCloudPlatform/gsutil/blob/c80f329bc3c4011236c78ce8910988773b2606cb/gslib/storage_url.py#L39. +std::string StripVersionHash(const std::string &filename) { + const std::string::size_type pos = filename.find_last_of('#'); + // Only strip if the hash is behind a possible file extension and the part + // behind the hash is a version string. + if (pos != std::string::npos && pos > filename.find_last_of('.') && + IsGcsVersion(filename.substr(pos + 1))) { + return filename.substr(0, pos); + } + return filename; +} +} // namespace + using namespace Assimp; // ------------------------------------------------------------------------------------------------ @@ -68,10 +93,6 @@ BaseImporter::BaseImporter() AI_NO_EXCEPT // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -BaseImporter::~BaseImporter() = default; - void BaseImporter::UpdateImporterScale(Importer *pImp) { ai_assert(pImp != nullptr); ai_assert(importerScale != 0.0); @@ -158,7 +179,7 @@ void BaseImporter::GetExtensionList(std::set &extensions) { std::size_t numTokens, unsigned int searchBytes /* = 200 */, bool tokensSol /* false */, - bool noAlphaBeforeTokens /* false */) { + bool noGraphBeforeTokens /* false */) { ai_assert(nullptr != tokens); ai_assert(0 != numTokens); ai_assert(0 != searchBytes); @@ -207,8 +228,9 @@ void BaseImporter::GetExtensionList(std::set &extensions) { continue; } // We need to make sure that we didn't accidentally identify the end of another token as our token, - // e.g. in a previous version the "gltf " present in some gltf files was detected as "f " - if (noAlphaBeforeTokens && (r != buffer && isalpha(static_cast(r[-1])))) { + // e.g. in a previous version the "gltf " present in some gltf files was detected as "f ", or a + // Blender-exported glb file containing "Khronos glTF Blender I/O " was detected as "o " + if (noGraphBeforeTokens && (r != buffer && isgraph(static_cast(r[-1])))) { continue; } // We got a match, either we don't care where it is, or it happens to @@ -228,34 +250,40 @@ void BaseImporter::GetExtensionList(std::set &extensions) { /*static*/ bool BaseImporter::SimpleExtensionCheck(const std::string &pFile, const char *ext0, const char *ext1, - const char *ext2) { - std::string::size_type pos = pFile.find_last_of('.'); - - // no file extension - can't read - if (pos == std::string::npos) { - return false; + const char *ext2, + const char *ext3) { + std::set extensions; + for (const char* ext : {ext0, ext1, ext2, ext3}) { + if (ext == nullptr) continue; + extensions.emplace(ext); } + return HasExtension(pFile, extensions); +} - const char *ext_real = &pFile[pos + 1]; - if (!ASSIMP_stricmp(ext_real, ext0)) { - return true; +// ------------------------------------------------------------------------------------------------ +// Check for file extension +/*static*/ bool BaseImporter::HasExtension(const std::string &pFile, const std::set &extensions) { + const std::string file = StripVersionHash(pFile); + // CAUTION: Do not just search for the extension! + // GetExtension() returns the part after the *last* dot, but some extensions + // have dots inside them, e.g. ogre.mesh.xml. Compare the entire end of the + // string. + for (const std::string& ext : extensions) { + // Yay for C++<20 not having std::string::ends_with() + const std::string dotExt = "." + ext; + if (dotExt.length() > file.length()) continue; + // Possible optimization: Fetch the lowercase filename! + if (0 == ASSIMP_stricmp(file.c_str() + file.length() - dotExt.length(), dotExt.c_str())) { + return true; + } } - - // check for other, optional, file extensions - if (ext1 && !ASSIMP_stricmp(ext_real, ext1)) { - return true; - } - - if (ext2 && !ASSIMP_stricmp(ext_real, ext2)) { - return true; - } - return false; } // ------------------------------------------------------------------------------------------------ // Get file extension from path -std::string BaseImporter::GetExtension(const std::string &file) { +std::string BaseImporter::GetExtension(const std::string &pFile) { + const std::string file = StripVersionHash(pFile); std::string::size_type pos = file.find_last_of('.'); // no file extension at all @@ -281,12 +309,7 @@ std::string BaseImporter::GetExtension(const std::string &file) { if (!pIOHandler) { return false; } - union { - const char *magic; - const uint16_t *magic_u16; - const uint32_t *magic_u32; - }; - magic = reinterpret_cast(_magic); + const char *magic = reinterpret_cast(_magic); std::unique_ptr pStream(pIOHandler->Open(pFile)); if (pStream) { @@ -308,15 +331,15 @@ std::string BaseImporter::GetExtension(const std::string &file) { // that's just for convenience, the chance that we cause conflicts // is quite low and it can save some lines and prevent nasty bugs if (2 == size) { - uint16_t rev = *magic_u16; - ByteSwap::Swap(&rev); - if (data_u16[0] == *magic_u16 || data_u16[0] == rev) { + uint16_t magic_u16; + memcpy(&magic_u16, magic, 2); + if (data_u16[0] == magic_u16 || data_u16[0] == ByteSwap::Swapped(magic_u16)) { return true; } } else if (4 == size) { - uint32_t rev = *magic_u32; - ByteSwap::Swap(&rev); - if (data_u32[0] == *magic_u32 || data_u32[0] == rev) { + uint32_t magic_u32; + memcpy(&magic_u32, magic, 4); + if (data_u32[0] == magic_u32 || data_u32[0] == ByteSwap::Swapped(magic_u32)) { return true; } } else { @@ -331,11 +354,7 @@ std::string BaseImporter::GetExtension(const std::string &file) { return false; } -#ifdef ASSIMP_USE_HUNTER -#include -#else -#include "../contrib/utf8cpp/source/utf8.h" -#endif +#include "utf8.h" // ------------------------------------------------------------------------------------------------ // Convert to UTF8 data diff --git a/Engine/lib/assimp/code/Common/BaseProcess.cpp b/Engine/lib/assimp/code/Common/BaseProcess.cpp index 8ff7c98e9..560ee7b94 100644 --- a/Engine/lib/assimp/code/Common/BaseProcess.cpp +++ b/Engine/lib/assimp/code/Common/BaseProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -57,10 +57,6 @@ BaseProcess::BaseProcess() AI_NO_EXCEPT // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -BaseProcess::~BaseProcess() = default; - // ------------------------------------------------------------------------------------------------ void BaseProcess::ExecuteOnScene(Importer *pImp) { ai_assert( nullptr != pImp ); diff --git a/Engine/lib/assimp/code/Common/BaseProcess.h b/Engine/lib/assimp/code/Common/BaseProcess.h index d2af4faa6..a945ad542 100644 --- a/Engine/lib/assimp/code/Common/BaseProcess.h +++ b/Engine/lib/assimp/code/Common/BaseProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -179,11 +179,11 @@ class ASSIMP_API BaseProcess { friend class Importer; public: - /** @brief onstructor to be privately used by Importer */ + /** @brief Constructor to be privately used by Importer */ BaseProcess() AI_NO_EXCEPT; - /** @brief Destructor, private as well */ - virtual ~BaseProcess(); + /** @brief Destructor */ + virtual ~BaseProcess() = default; // ------------------------------------------------------------------- /** diff --git a/Engine/lib/assimp/code/Common/Bitmap.cpp b/Engine/lib/assimp/code/Common/Bitmap.cpp index 65dfd1754..e6732c528 100644 --- a/Engine/lib/assimp/code/Common/Bitmap.cpp +++ b/Engine/lib/assimp/code/Common/Bitmap.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -148,8 +148,10 @@ void Bitmap::WriteData(aiTexture *texture, IOStream *file) { file->Write(pixel, mBytesPerPixel, 1); } - - file->Write(padding_data, padding, 1); + // When padding is 0, passing it as an argument will cause an assertion failure in DefaultIOStream::Write. + if (padding) { + file->Write(padding_data, padding, 1); + } } } diff --git a/Engine/lib/assimp/code/Common/Compression.cpp b/Engine/lib/assimp/code/Common/Compression.cpp index 3bf306dee..091171771 100644 --- a/Engine/lib/assimp/code/Common/Compression.cpp +++ b/Engine/lib/assimp/code/Common/Compression.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -66,6 +66,10 @@ Compression::Compression() : Compression::~Compression() { ai_assert(mImpl != nullptr); + if (mImpl->mOpen) { + close(); + } + delete mImpl; } @@ -124,7 +128,7 @@ static int getFlushMode(Compression::FlushMode flush) { return z_flush; } -constexpr size_t MYBLOCK = 32786; +static constexpr size_t MYBLOCK = 32786; size_t Compression::decompress(const void *data, size_t in, std::vector &uncompressed) { ai_assert(mImpl != nullptr); diff --git a/Engine/lib/assimp/code/Common/Compression.h b/Engine/lib/assimp/code/Common/Compression.h index edf1d232f..0bec91bba 100644 --- a/Engine/lib/assimp/code/Common/Compression.h +++ b/Engine/lib/assimp/code/Common/Compression.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,11 +41,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once -#ifdef ASSIMP_BUILD_NO_OWN_ZLIB -#include -#else -#include "../contrib/zlib/zlib.h" -#endif +#include "zlib.h" #include #include // size_t diff --git a/Engine/lib/assimp/code/Common/CreateAnimMesh.cpp b/Engine/lib/assimp/code/Common/CreateAnimMesh.cpp index 58f3d909d..467651587 100644 --- a/Engine/lib/assimp/code/Common/CreateAnimMesh.cpp +++ b/Engine/lib/assimp/code/Common/CreateAnimMesh.cpp @@ -4,7 +4,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- Copyright (C) 2016 The Qt Company Ltd. -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/DefaultIOStream.cpp b/Engine/lib/assimp/code/Common/DefaultIOStream.cpp index f2c772187..e423eae4f 100644 --- a/Engine/lib/assimp/code/Common/DefaultIOStream.cpp +++ b/Engine/lib/assimp/code/Common/DefaultIOStream.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/DefaultIOSystem.cpp b/Engine/lib/assimp/code/Common/DefaultIOSystem.cpp index b28910c70..4eedcb0b6 100644 --- a/Engine/lib/assimp/code/Common/DefaultIOSystem.cpp +++ b/Engine/lib/assimp/code/Common/DefaultIOSystem.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -93,18 +93,24 @@ static std::string WideToUtf8(const wchar_t *in) { // ------------------------------------------------------------------------------------------------ // Tests for the existence of a file at the given path. bool DefaultIOSystem::Exists(const char *pFile) const { + if (pFile == nullptr) { + return false; + } + #ifdef _WIN32 struct __stat64 filestat; if (_wstat64(Utf8ToWide(pFile).c_str(), &filestat) != 0) { return false; } #else - FILE *file = ::fopen(pFile, "rb"); - if (!file) { + struct stat statbuf; + if (stat(pFile, &statbuf) != 0) { + return false; + } + // test for a regular file + if (!S_ISREG(statbuf.st_mode)) { return false; } - - ::fclose(file); #endif return true; @@ -116,6 +122,7 @@ IOStream *DefaultIOSystem::Open(const char *strFile, const char *strMode) { ai_assert(strFile != nullptr); ai_assert(strMode != nullptr); FILE *file; + #ifdef _WIN32 std::wstring name = Utf8ToWide(strFile); if (name.empty()) { @@ -126,6 +133,7 @@ IOStream *DefaultIOSystem::Open(const char *strFile, const char *strMode) { #else file = ::fopen(strFile, strMode); #endif + if (!file) { return nullptr; } diff --git a/Engine/lib/assimp/code/Common/DefaultLogger.cpp b/Engine/lib/assimp/code/Common/DefaultLogger.cpp index 5cb32d38f..744e0ba79 100644 --- a/Engine/lib/assimp/code/Common/DefaultLogger.cpp +++ b/Engine/lib/assimp/code/Common/DefaultLogger.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -221,13 +221,11 @@ void DefaultLogger::set(Logger *logger) { #endif if (nullptr == logger) { - logger = &s_pNullLogger; + m_pLogger = &s_pNullLogger; } - if (nullptr != m_pLogger && !isNullLogger()) { - delete m_pLogger; + else { + m_pLogger = logger; } - - DefaultLogger::m_pLogger = logger; } // ---------------------------------------------------------------------------------- @@ -320,9 +318,13 @@ bool DefaultLogger::attachStream(LogStream *pStream, unsigned int severity) { } if (0 == severity) { - severity = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging; + severity = SeverityAll; } +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard lock(m_arrayMutex); +#endif + for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { @@ -348,6 +350,10 @@ bool DefaultLogger::detachStream(LogStream *pStream, unsigned int severity) { severity = SeverityAll; } +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard lock(m_arrayMutex); +#endif + bool res(false); for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { if ((*it)->m_pStream == pStream) { @@ -387,6 +393,10 @@ DefaultLogger::~DefaultLogger() { void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev) { ai_assert(nullptr != message); +#ifndef ASSIMP_BUILD_SINGLETHREADED + std::lock_guard lock(m_arrayMutex); +#endif + // Check whether this is a repeated message auto thisLen = ::strlen(message); if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) { diff --git a/Engine/lib/assimp/code/Common/DefaultProgressHandler.h b/Engine/lib/assimp/code/Common/DefaultProgressHandler.h index ac1bb68db..2ace9e02a 100644 --- a/Engine/lib/assimp/code/Common/DefaultProgressHandler.h +++ b/Engine/lib/assimp/code/Common/DefaultProgressHandler.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/Exceptional.cpp b/Engine/lib/assimp/code/Common/Exceptional.cpp index b25281a97..0629f716e 100644 --- a/Engine/lib/assimp/code/Common/Exceptional.cpp +++ b/Engine/lib/assimp/code/Common/Exceptional.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/Exporter.cpp b/Engine/lib/assimp/code/Common/Exporter.cpp index b25b37d57..4da055064 100644 --- a/Engine/lib/assimp/code/Common/Exporter.cpp +++ b/Engine/lib/assimp/code/Common/Exporter.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -225,7 +225,7 @@ static void setupExporterArray(std::vector &exporte #endif #ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER - exporters.emplace_back("pbrt", "pbrt-v4 scene description file", "pbrt", &ExportScenePbrt, aiProcess_Triangulate | aiProcess_SortByPType); + exporters.emplace_back("pbrt", "pbrt-v4 scene description file", "pbrt", &ExportScenePbrt, aiProcess_ConvertToLeftHanded | aiProcess_Triangulate | aiProcess_SortByPType); #endif #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER @@ -595,7 +595,7 @@ bool ExportProperties::SetPropertyCallback(const char *szName, const std::functi } std::function ExportProperties::GetPropertyCallback(const char *szName) const { - return GetGenericProperty>(mCallbackProperties, szName, 0); + return GetGenericProperty>(mCallbackProperties, szName, nullptr); } bool ExportProperties::HasPropertyCallback(const char *szName) const { diff --git a/Engine/lib/assimp/code/Common/FileLogStream.h b/Engine/lib/assimp/code/Common/FileLogStream.h index 334541485..f64f88f48 100644 --- a/Engine/lib/assimp/code/Common/FileLogStream.h +++ b/Engine/lib/assimp/code/Common/FileLogStream.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/FileSystemFilter.h b/Engine/lib/assimp/code/Common/FileSystemFilter.h index 9ab3812e2..c530153d4 100644 --- a/Engine/lib/assimp/code/Common/FileSystemFilter.h +++ b/Engine/lib/assimp/code/Common/FileSystemFilter.h @@ -93,9 +93,7 @@ public: } /** Destructor. */ - ~FileSystemFilter() { - // empty - } + ~FileSystemFilter() = default; // ------------------------------------------------------------------- /** Tests for the existence of a file at the given path. */ @@ -299,7 +297,7 @@ private: } const char separator = getOsSeparator(); - for (it = in.begin(); it != in.end(); ++it) { + for (it = in.begin(); it < in.end(); ++it) { const size_t remaining = std::distance(in.end(), it); // Exclude :// and \\, which remain untouched. // https://sourceforge.net/tracker/?func=detail&aid=3031725&group_id=226462&atid=1067632 diff --git a/Engine/lib/assimp/code/Common/IFF.h b/Engine/lib/assimp/code/Common/IFF.h index cf07f9833..d5469a760 100644 --- a/Engine/lib/assimp/code/Common/IFF.h +++ b/Engine/lib/assimp/code/Common/IFF.h @@ -122,7 +122,7 @@ inline const char* ReadHeader(uint8_t* outFile, uint32_t& fileType) } ::memcpy(&fileType, outFile, 4); AI_LSWAP4(fileType); - return 0; + return nullptr; } diff --git a/Engine/lib/assimp/code/Common/IOSystem.cpp b/Engine/lib/assimp/code/Common/IOSystem.cpp index 1e63827ba..aa91e9b49 100644 --- a/Engine/lib/assimp/code/Common/IOSystem.cpp +++ b/Engine/lib/assimp/code/Common/IOSystem.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/Importer.cpp b/Engine/lib/assimp/code/Common/Importer.cpp index 52b6097e7..46dfb3358 100644 --- a/Engine/lib/assimp/code/Common/Importer.cpp +++ b/Engine/lib/assimp/code/Common/Importer.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -482,37 +482,43 @@ bool Importer::ValidateFlags(unsigned int pFlags) const { } // ------------------------------------------------------------------------------------------------ -const aiScene* Importer::ReadFileFromMemory( const void* pBuffer, - size_t pLength, - unsigned int pFlags, - const char* pHint /*= ""*/) { +const aiScene* Importer::ReadFileFromMemory(const void* pBuffer, size_t pLength, unsigned int pFlags, const char* pHint ) { ai_assert(nullptr != pimpl); - ASSIMP_BEGIN_EXCEPTION_REGION(); - if (!pHint) { - pHint = ""; - } - - if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) { - pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()"; - return nullptr; - } - - // prevent deletion of the previous IOHandler IOSystem* io = pimpl->mIOHandler; - pimpl->mIOHandler = nullptr; + try { + if (pHint == nullptr) { + pHint = ""; + } + if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) { + pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()"; + return nullptr; + } + // prevent deletion of the previous IOHandler + pimpl->mIOHandler = nullptr; - SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io)); + SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io)); - // read the file and recover the previous IOSystem - static const size_t BufSize(Importer::MaxLenHint + 28); - char fbuff[BufSize]; - ai_snprintf(fbuff, BufSize, "%s.%s",AI_MEMORYIO_MAGIC_FILENAME,pHint); + // read the file and recover the previous IOSystem + static const size_t BufSize(Importer::MaxLenHint + 28); + char fbuff[BufSize]; + ai_snprintf(fbuff, BufSize, "%s.%s",AI_MEMORYIO_MAGIC_FILENAME,pHint); - ReadFile(fbuff,pFlags); - SetIOHandler(io); + ReadFile(fbuff,pFlags); + SetIOHandler(io); + } catch(const DeadlyImportError &e) { + pimpl->mErrorString = e.what(); + pimpl->mException = std::current_exception(); + SetIOHandler(io); + return ExceptionSwallower()(); \ + } catch(...) { + pimpl->mErrorString = "Unknown exception"; + pimpl->mException = std::current_exception(); + SetIOHandler(io); + return ExceptionSwallower()(); \ + + } - ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString, pimpl->mException); return pimpl->mScene; } @@ -631,24 +637,10 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { std::set extensions; pimpl->mImporter[a]->GetExtensionList(extensions); - // CAUTION: Do not just search for the extension! - // GetExtension() returns the part after the *last* dot, but some extensions have dots - // inside them, e.g. ogre.mesh.xml. Compare the entire end of the string. - for (std::set::const_iterator it = extensions.cbegin(); it != extensions.cend(); ++it) { - - // Yay for C++<20 not having std::string::ends_with() - std::string extension = "." + *it; - if (extension.length() <= pFile.length()) { - // Possible optimization: Fetch the lowercase filename! - if (0 == ASSIMP_stricmp(pFile.c_str() + pFile.length() - extension.length(), extension.c_str())) { - ImporterAndIndex candidate = { pimpl->mImporter[a], a }; - possibleImporters.push_back(candidate); - break; - } - } - + if (BaseImporter::HasExtension(pFile, extensions)) { + ImporterAndIndex candidate = { pimpl->mImporter[a], a }; + possibleImporters.push_back(candidate); } - } // If just one importer supports this extension, pick it and close the case. @@ -779,6 +771,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { #else pimpl->mErrorString = std::string("std::exception: ") + e.what(); #endif + pimpl->mException = std::current_exception(); ASSIMP_LOG_ERROR(pimpl->mErrorString); delete pimpl->mScene; pimpl->mScene = nullptr; @@ -856,11 +849,7 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { break; } #ifdef ASSIMP_BUILD_DEBUG - -#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS - continue; -#endif // no validation - +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step if (pimpl->bExtraVerbose) { ASSIMP_LOG_DEBUG("Verbose Import: re-validating data structures"); @@ -872,6 +861,7 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { break; } } +#endif // no validation #endif // ! DEBUG } pimpl->mProgressHandler->UpdatePostProcess( static_cast(pimpl->mPostProcessingSteps.size()), @@ -947,6 +937,7 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess profiler->EndRegion( "postprocess" ); } +#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step if ( pimpl->bExtraVerbose || requestValidation ) { ASSIMP_LOG_DEBUG( "Verbose Import: revalidating data structures" ); @@ -957,6 +948,7 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess ASSIMP_LOG_ERROR( "Verbose Import: failed to revalidate data structures" ); } } +#endif // no validation // clear any data allocated by post-process steps pimpl->mPPShared->Clean(); @@ -1118,7 +1110,7 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { // Set a configuration property bool Importer::SetPropertyPointer(const char* szName, void* value) { ai_assert(nullptr != pimpl); - + bool existing; ASSIMP_BEGIN_EXCEPTION_REGION(); existing = SetGenericProperty(pimpl->mPointerProperties, szName,value); @@ -1162,7 +1154,7 @@ aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& i // Get a configuration property void* Importer::GetPropertyPointer(const char* szName, void* iErrorReturn /*= nullptr*/) const { ai_assert(nullptr != pimpl); - + return GetGenericProperty(pimpl->mPointerProperties,szName,iErrorReturn); } diff --git a/Engine/lib/assimp/code/Common/Importer.h b/Engine/lib/assimp/code/Common/Importer.h index ab7e3a6b3..2da55f173 100644 --- a/Engine/lib/assimp/code/Common/Importer.h +++ b/Engine/lib/assimp/code/Common/Importer.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -74,11 +74,11 @@ public: typedef unsigned int KeyType; // typedefs for our configuration maps. - typedef std::map IntPropertyMap; - typedef std::map FloatPropertyMap; - typedef std::map StringPropertyMap; - typedef std::map MatrixPropertyMap; - typedef std::map PointerPropertyMap; + using IntPropertyMap = std::map; + using FloatPropertyMap = std::map; + using StringPropertyMap = std::map; + using MatrixPropertyMap = std::map; + using PointerPropertyMap = std::map; /** IO handler to use for all file accesses. */ IOSystem* mIOHandler; @@ -128,10 +128,12 @@ public: /// The default class constructor. ImporterPimpl() AI_NO_EXCEPT; + + /// The class destructor. + ~ImporterPimpl() = default; }; -inline -ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT : +inline ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT : mIOHandler( nullptr ), mIsDefaultHandler( false ), mProgressHandler( nullptr ), diff --git a/Engine/lib/assimp/code/Common/ImporterRegistry.cpp b/Engine/lib/assimp/code/Common/ImporterRegistry.cpp index 78c02d96d..276f20974 100644 --- a/Engine/lib/assimp/code/Common/ImporterRegistry.cpp +++ b/Engine/lib/assimp/code/Common/ImporterRegistry.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,6 +55,9 @@ corresponding preprocessor flag to selectively disable formats. // Importers // (include_new_importers_here) // ------------------------------------------------------------------------------------------------ +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) +#include "AssetLib/USD/USDLoader.h" +#endif #ifndef ASSIMP_BUILD_NO_X_IMPORTER #include "AssetLib/X/XFileImporter.h" #endif @@ -214,7 +217,12 @@ void GetImporterInstanceList(std::vector &out) { // Some importers may be unimplemented or otherwise unsuitable for general use // in their current state. Devs can set ASSIMP_ENABLE_DEV_IMPORTERS in their // local environment to enable them, otherwise they're left out of the registry. +#if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP + // not supported under uwp const char *envStr = std::getenv("ASSIMP_ENABLE_DEV_IMPORTERS"); +#else + const char *envStr = { "0" }; +#endif bool devImportersEnabled = envStr && strcmp(envStr, "0"); // Ensure no unused var warnings if all uses are #ifndef'd away below: @@ -225,6 +233,9 @@ void GetImporterInstanceList(std::vector &out) { // (register_new_importers_here) // ---------------------------------------------------------------------------- out.reserve(64); +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) + out.push_back(new USDImporter()); +#endif #if (!defined ASSIMP_BUILD_NO_X_IMPORTER) out.push_back(new XFileImporter()); #endif @@ -377,9 +388,6 @@ void GetImporterInstanceList(std::vector &out) { #ifndef ASSIMP_BUILD_NO_IQM_IMPORTER out.push_back(new IQMImporter()); #endif - //#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER - // out.push_back(new StepFile::StepFileImporter()); - //#endif } /** will delete all registered importers. */ diff --git a/Engine/lib/assimp/code/Common/Maybe.h b/Engine/lib/assimp/code/Common/Maybe.h index b23865b86..99b18b67c 100644 --- a/Engine/lib/assimp/code/Common/Maybe.h +++ b/Engine/lib/assimp/code/Common/Maybe.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,6 +41,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once #include +#include namespace Assimp { @@ -48,13 +49,14 @@ namespace Assimp { /// @tparam T The type to store. template struct Maybe { - /// @brief + /// @brief Maybe() = default; - /// @brief - /// @param val - explicit Maybe(const T &val) : - _val(val), _valid(true) {} + /// @brief + /// @param val + template + explicit Maybe(U &&val) : + _val(std::forward(val)), _valid(true) {} /// @brief Validate the value /// @return true if valid. @@ -64,11 +66,12 @@ struct Maybe { /// @brief Will assign a value. /// @param v The new valid value. - void Set(T &v) { + template + void Set(U &&v) { ai_assert(!_valid); _valid = true; - _val = v; + _val = std::forward(v); } /// @brief Will return the value when it is valid. diff --git a/Engine/lib/assimp/code/Common/PolyTools.h b/Engine/lib/assimp/code/Common/PolyTools.h index 11f627392..46ceb9d75 100644 --- a/Engine/lib/assimp/code/Common/PolyTools.h +++ b/Engine/lib/assimp/code/Common/PolyTools.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -64,8 +64,14 @@ inline double GetArea2D(const T& v1, const T& v2, const T& v3) { * The function accepts an unconstrained template parameter for use with * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ template -inline bool OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) { - return GetArea2D(p0,p2,p1) > 0; +inline int OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) { + double area = GetArea2D(p0,p2,p1); + if(std::abs(area) < ai_epsilon) + return 0; + else if(area > 0) + return 1; + else + return -1; } // ------------------------------------------------------------------------------- @@ -74,26 +80,11 @@ inline bool OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) { * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ template inline bool PointInTriangle2D(const T& p0, const T& p1,const T& p2, const T& pp) { - // Point in triangle test using baryzentric coordinates - const aiVector2D v0 = p1 - p0; - const aiVector2D v1 = p2 - p0; - const aiVector2D v2 = pp - p0; - - double dot00 = v0 * v0; - double dot11 = v1 * v1; - const double dot01 = v0 * v1; - const double dot02 = v0 * v2; - const double dot12 = v1 * v2; - const double denom = dot00 * dot11 - dot01 * dot01; - if (denom == 0.0) { - return false; - } - - const double invDenom = 1.0 / denom; - dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; - dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; - - return (dot11 > 0) && (dot00 > 0) && (dot11 + dot00 < 1); + // pp should be left side of the three triangle side, by ccw arrow + int c1 = OnLeftSideOfLine2D(p0, p1, pp); + int c2 = OnLeftSideOfLine2D(p1, p2, pp); + int c3 = OnLeftSideOfLine2D(p2, p0, pp); + return (c1 >= 0) && (c2 >= 0) && (c3 >= 0); } @@ -128,7 +119,7 @@ inline bool IsCCW(T* in, size_t npoints) { c = std::sqrt(cc); theta = std::acos((bb + cc - aa) / (2 * b * c)); - if (OnLeftSideOfLine2D(in[i],in[i+2],in[i+1])) { + if (OnLeftSideOfLine2D(in[i],in[i+2],in[i+1]) == 1) { // if (convex(in[i].x, in[i].y, // in[i+1].x, in[i+1].y, // in[i+2].x, in[i+2].y)) { @@ -158,7 +149,7 @@ inline bool IsCCW(T* in, size_t npoints) { //if (convex(in[npoints-2].x, in[npoints-2].y, // in[0].x, in[0].y, // in[1].x, in[1].y)) { - if (OnLeftSideOfLine2D(in[npoints-2],in[1],in[0])) { + if (OnLeftSideOfLine2D(in[npoints-2],in[1],in[0]) == 1) { convex_turn = AI_MATH_PI_F - theta; convex_sum += convex_turn; } else { diff --git a/Engine/lib/assimp/code/Common/PostStepRegistry.cpp b/Engine/lib/assimp/code/Common/PostStepRegistry.cpp index de4f39083..fdf33fc40 100644 --- a/Engine/lib/assimp/code/Common/PostStepRegistry.cpp +++ b/Engine/lib/assimp/code/Common/PostStepRegistry.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team diff --git a/Engine/lib/assimp/code/Common/RemoveComments.cpp b/Engine/lib/assimp/code/Common/RemoveComments.cpp index 4fae21c95..a6a472400 100644 --- a/Engine/lib/assimp/code/Common/RemoveComments.cpp +++ b/Engine/lib/assimp/code/Common/RemoveComments.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -39,7 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** +/** * @file RemoveComments.cpp * @brief Defines the CommentRemover utility class */ diff --git a/Engine/lib/assimp/code/Common/SGSpatialSort.cpp b/Engine/lib/assimp/code/Common/SGSpatialSort.cpp index 0ef1853c0..d24ecf1b4 100644 --- a/Engine/lib/assimp/code/Common/SGSpatialSort.cpp +++ b/Engine/lib/assimp/code/Common/SGSpatialSort.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -50,16 +48,13 @@ the 3ds loader handling smooth groups correctly */ using namespace Assimp; // ------------------------------------------------------------------------------------------------ -SGSpatialSort::SGSpatialSort() -{ +SGSpatialSort::SGSpatialSort() { // define the reference plane. We choose some arbitrary vector away from all basic axes // in the hope that no model spreads all its vertices along this plane. mPlaneNormal.Set( 0.8523f, 0.34321f, 0.5736f); mPlaneNormal.Normalize(); } -// ------------------------------------------------------------------------------------------------ -// Destructor -SGSpatialSort::~SGSpatialSort() = default; + // ------------------------------------------------------------------------------------------------ void SGSpatialSort::Add(const aiVector3D& vPosition, unsigned int index, unsigned int smoothingGroup) diff --git a/Engine/lib/assimp/code/Common/SceneCombiner.cpp b/Engine/lib/assimp/code/Common/SceneCombiner.cpp index 0f5386b26..a261f459e 100644 --- a/Engine/lib/assimp/code/Common/SceneCombiner.cpp +++ b/Engine/lib/assimp/code/Common/SceneCombiner.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -63,6 +63,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { @@ -78,7 +79,7 @@ inline void PrefixString(aiString &string, const char *prefix, unsigned int len) if (string.length >= 1 && string.data[0] == '$') return; - if (len + string.length >= MAXLEN - 1) { + if (len + string.length >= AI_MAXLEN - 1) { ASSIMP_LOG_VERBOSE_DEBUG("Can't add an unique prefix because the string is too long"); ai_assert(false); return; @@ -252,6 +253,14 @@ void SceneCombiner::AttachToGraph(aiScene *master, std::vector &srcList, unsigned int flags) { if (nullptr == _dest) { + std::unordered_set uniqueScenes; + uniqueScenes.insert(master); + for (const auto &item : srcList) { + uniqueScenes.insert(item.scene); + } + for (const auto &item : uniqueScenes) { + delete item; + } return; } @@ -259,6 +268,7 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector > rndGen(rng, dist); #endif for (unsigned int i = 1; i < src.size(); ++i) { - //if (i != duplicates[i]) - //{ - // // duplicate scenes share the same UID - // ::strcpy( src[i].id, src[duplicates[i]].id ); - // src[i].idlen = src[duplicates[i]].idlen; - - // continue; - //} - src[i].idlen = ai_snprintf(src[i].id, 32, "$%.6X$_", i); if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { @@ -408,7 +409,7 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vectormData. The size of mData is not guaranteed to be - // MAXLEN in size. + // AI_MAXLEN in size. aiString s(*(aiString *)prop->mData); if ('*' == s.data[0]) { // Offset the index and write it back .. @@ -1057,7 +1058,7 @@ void SceneCombiner::CopyScene(aiScene **_dest, const aiScene *src, bool allocate dest->mFlags = src->mFlags; // source private data might be nullptr if the scene is user-allocated (i.e. for use with the export API) - if (dest->mPrivate != nullptr) { + if (src->mPrivate != nullptr) { ScenePriv(dest)->mPPStepsApplied = ScenePriv(src) ? ScenePriv(src)->mPPStepsApplied : 0; } } @@ -1349,6 +1350,9 @@ void SceneCombiner::Copy(aiMetadata **_dest, const aiMetadata *src) { case AI_AIVECTOR3D: out.mData = new aiVector3D(*static_cast(in.mData)); break; + case AI_AIMETADATA: + out.mData = new aiMetadata(*static_cast(in.mData)); + break; default: ai_assert(false); break; diff --git a/Engine/lib/assimp/code/Common/ScenePreprocessor.cpp b/Engine/lib/assimp/code/Common/ScenePreprocessor.cpp index c769ec30c..b4bbe9d67 100644 --- a/Engine/lib/assimp/code/Common/ScenePreprocessor.cpp +++ b/Engine/lib/assimp/code/Common/ScenePreprocessor.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -106,7 +106,7 @@ void ScenePreprocessor::ProcessMesh(aiMesh *mesh) { if (!mesh->mTextureCoords[i]) { mesh->mNumUVComponents[i] = 0; continue; - } + } if (!mesh->mNumUVComponents[i]) { mesh->mNumUVComponents[i] = 2; @@ -142,6 +142,7 @@ void ScenePreprocessor::ProcessMesh(aiMesh *mesh) { // If the information which primitive types are there in the // mesh is currently not available, compute it. if (!mesh->mPrimitiveTypes) { + ai_assert(mesh->mFaces != nullptr); for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { aiFace &face = mesh->mFaces[a]; switch (face.mNumIndices) { diff --git a/Engine/lib/assimp/code/Common/ScenePreprocessor.h b/Engine/lib/assimp/code/Common/ScenePreprocessor.h index 49e06ed1c..6ffd7f8bd 100644 --- a/Engine/lib/assimp/code/Common/ScenePreprocessor.h +++ b/Engine/lib/assimp/code/Common/ScenePreprocessor.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,7 +55,7 @@ class ScenePreprocessorTest; namespace Assimp { // ---------------------------------------------------------------------------------- -/** ScenePreprocessor: Preprocess a scene before any post-processing +/** ScenePreprocessor: Pre-process a scene before any post-processing * steps are executed. * * The step computes data that needn't necessarily be provided by the @@ -79,6 +78,9 @@ public: ScenePreprocessor(aiScene *_scene) : scene(_scene) {} + /// @brief The class destructor. + ~ScenePreprocessor() = default; + // ---------------------------------------------------------------- /** Assign a (new) scene to the object. * diff --git a/Engine/lib/assimp/code/Common/ScenePrivate.h b/Engine/lib/assimp/code/Common/ScenePrivate.h index 3910db78c..b8c524aa2 100644 --- a/Engine/lib/assimp/code/Common/ScenePrivate.h +++ b/Engine/lib/assimp/code/Common/ScenePrivate.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/SkeletonMeshBuilder.cpp b/Engine/lib/assimp/code/Common/SkeletonMeshBuilder.cpp index bdb926b75..167652700 100644 --- a/Engine/lib/assimp/code/Common/SkeletonMeshBuilder.cpp +++ b/Engine/lib/assimp/code/Common/SkeletonMeshBuilder.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -131,32 +131,33 @@ void SkeletonMeshBuilder::CreateGeometry(const aiNode *pNode) { // if the node has no children, it's an end node. Put a little knob there instead aiVector3D ownpos(pNode->mTransformation.a4, pNode->mTransformation.b4, pNode->mTransformation.c4); ai_real sizeEstimate = ownpos.Length() * ai_real(0.18); + const ai_real zero(0.0); - mVertices.emplace_back(-sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, sizeEstimate, 0.0); - mVertices.emplace_back(0.0, 0.0, -sizeEstimate); - mVertices.emplace_back(0.0, sizeEstimate, 0.0); - mVertices.emplace_back(sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, 0.0, -sizeEstimate); - mVertices.emplace_back(sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, -sizeEstimate, 0.0); - mVertices.emplace_back(0.0, 0.0, -sizeEstimate); - mVertices.emplace_back(0.0, -sizeEstimate, 0.0); - mVertices.emplace_back(-sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, 0.0, -sizeEstimate); + mVertices.emplace_back(-sizeEstimate, zero, zero); + mVertices.emplace_back(zero, sizeEstimate, zero); + mVertices.emplace_back(zero, zero, -sizeEstimate); + mVertices.emplace_back(zero, sizeEstimate, zero); + mVertices.emplace_back(sizeEstimate, zero, zero); + mVertices.emplace_back(zero, zero, -sizeEstimate); + mVertices.emplace_back(sizeEstimate, zero, zero); + mVertices.emplace_back(zero, -sizeEstimate, zero); + mVertices.emplace_back(zero, zero, -sizeEstimate); + mVertices.emplace_back(zero, -sizeEstimate, zero); + mVertices.emplace_back(-sizeEstimate, zero, zero); + mVertices.emplace_back(zero, zero, -sizeEstimate); - mVertices.emplace_back(-sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, 0.0, sizeEstimate); - mVertices.emplace_back(0.0, sizeEstimate, 0.0); - mVertices.emplace_back(0.0, sizeEstimate, 0.0); - mVertices.emplace_back(0.0, 0.0, sizeEstimate); - mVertices.emplace_back(sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(sizeEstimate, 0.0, 0.0); - mVertices.emplace_back(0.0, 0.0, sizeEstimate); - mVertices.emplace_back(0.0, -sizeEstimate, 0.0); - mVertices.emplace_back(0.0, -sizeEstimate, 0.0); - mVertices.emplace_back(0.0, 0.0, sizeEstimate); - mVertices.emplace_back(-sizeEstimate, 0.0, 0.0); + mVertices.emplace_back(-sizeEstimate, zero, zero); + mVertices.emplace_back(zero, zero, sizeEstimate); + mVertices.emplace_back(zero, sizeEstimate, zero); + mVertices.emplace_back(zero, sizeEstimate, zero); + mVertices.emplace_back(zero, zero, sizeEstimate); + mVertices.emplace_back(sizeEstimate, zero, zero); + mVertices.emplace_back(sizeEstimate, zero, zero); + mVertices.emplace_back(zero, zero, sizeEstimate); + mVertices.emplace_back(zero, -sizeEstimate, zero); + mVertices.emplace_back(zero, -sizeEstimate, zero); + mVertices.emplace_back(zero, zero, sizeEstimate); + mVertices.emplace_back(-sizeEstimate, zero, zero); mFaces.emplace_back(vertexStartIndex + 0, vertexStartIndex + 1, vertexStartIndex + 2); mFaces.emplace_back(vertexStartIndex + 3, vertexStartIndex + 4, vertexStartIndex + 5); diff --git a/Engine/lib/assimp/code/Common/SpatialSort.cpp b/Engine/lib/assimp/code/Common/SpatialSort.cpp index a35ebb055..6bce63af4 100644 --- a/Engine/lib/assimp/code/Common/SpatialSort.cpp +++ b/Engine/lib/assimp/code/Common/SpatialSort.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -71,10 +71,6 @@ SpatialSort::SpatialSort() : mPlaneNormal.Normalize(); } -// ------------------------------------------------------------------------------------------------ -// Destructor -SpatialSort::~SpatialSort() = default; - // ------------------------------------------------------------------------------------------------ void SpatialSort::Fill(const aiVector3D *pPositions, unsigned int pNumPositions, unsigned int pElementOffset, @@ -94,7 +90,7 @@ ai_real SpatialSort::CalculateDistance(const aiVector3D &pPosition) const { void SpatialSort::Finalize() { const ai_real scale = 1.0f / mPositions.size(); for (unsigned int i = 0; i < mPositions.size(); i++) { - mCentroid += scale * mPositions[i].mPosition; + mCentroid += scale * mPositions[i].mPosition; } for (unsigned int i = 0; i < mPositions.size(); i++) { mPositions[i].mDistance = CalculateDistance(mPositions[i].mPosition); diff --git a/Engine/lib/assimp/code/Common/StackAllocator.h b/Engine/lib/assimp/code/Common/StackAllocator.h new file mode 100644 index 000000000..fcb72a09e --- /dev/null +++ b/Engine/lib/assimp/code/Common/StackAllocator.h @@ -0,0 +1,98 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------- +*/ + +/** @file StackAllocator.h + * @brief A very bare-bone allocator class that is suitable when + * allocating many small objects, e.g. during parsing. + * Individual objects are not freed, instead only the whole memory + * can be deallocated. + */ +#ifndef AI_STACK_ALLOCATOR_H_INC +#define AI_STACK_ALLOCATOR_H_INC + +#include +#include +#include + +namespace Assimp { + +/** @brief A very bare-bone allocator class that is suitable when + * allocating many small objects, e.g. during parsing. + * Individual objects are not freed, instead only the whole memory + * can be deallocated. +*/ +class StackAllocator { +public: + /// @brief Constructs the allocator + StackAllocator(); + + /// @brief Destructs the allocator and frees all memory + ~StackAllocator(); + + // non copyable + StackAllocator(const StackAllocator &) = delete; + StackAllocator &operator=(const StackAllocator &) = delete; + + /// @brief Returns a pointer to byteSize bytes of heap memory that persists + /// for the lifetime of the allocator (or until FreeAll is called). + inline void *Allocate(size_t byteSize); + + /// @brief Releases all the memory owned by this allocator. + // Memory provided through function Allocate is not valid anymore after this function has been called. + inline void FreeAll(); + +private: + constexpr const static size_t g_maxBytesPerBlock = 64 * 1024 * 1024; // The maximum size (in bytes) of a block + constexpr const static size_t g_startBytesPerBlock = 16 * 1024; // Size of the first block. Next blocks will double in size until maximum size of g_maxBytesPerBlock + size_t m_blockAllocationSize = g_startBytesPerBlock; // Block size of the current block + size_t m_subIndex = g_maxBytesPerBlock; // The current byte offset in the current block + std::vector m_storageBlocks; // A list of blocks +}; + +} // namespace Assimp + +/// @brief Fixes an undefined reference error when linking in certain build environments. +// May throw warnings about needing stdc++17, but should compile without issues on modern compilers. +inline const size_t Assimp::StackAllocator::g_maxBytesPerBlock; +inline const size_t Assimp::StackAllocator::g_startBytesPerBlock; + +#include "StackAllocator.inl" + +#endif // include guard diff --git a/Engine/lib/assimp/code/Common/StackAllocator.inl b/Engine/lib/assimp/code/Common/StackAllocator.inl new file mode 100644 index 000000000..44f15edbc --- /dev/null +++ b/Engine/lib/assimp/code/Common/StackAllocator.inl @@ -0,0 +1,82 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#include "StackAllocator.h" +#include +#include + +using namespace Assimp; + +inline StackAllocator::StackAllocator() : m_storageBlocks() {} + +inline StackAllocator::~StackAllocator() { + FreeAll(); +} + +inline void *StackAllocator::Allocate(size_t byteSize) { + if (m_subIndex + byteSize > m_blockAllocationSize) // start a new block + { + // double block size every time, up to maximum of g_maxBytesPerBlock. + // Block size must be at least as large as byteSize, but we want to use this for small allocations anyway. + m_blockAllocationSize = std::max(std::min(m_blockAllocationSize * 2, g_maxBytesPerBlock), byteSize); + uint8_t *data = new uint8_t[m_blockAllocationSize]; + m_storageBlocks.emplace_back(data); + m_subIndex = byteSize; + return data; + } + + uint8_t *data = m_storageBlocks.back(); + data += m_subIndex; + m_subIndex += byteSize; + + return data; +} + +inline void StackAllocator::FreeAll() { + for (size_t i = 0; i < m_storageBlocks.size(); i++) { + delete [] m_storageBlocks[i]; + } + std::vector empty; + m_storageBlocks.swap(empty); + // start over: + m_blockAllocationSize = g_startBytesPerBlock; + m_subIndex = g_maxBytesPerBlock; +} diff --git a/Engine/lib/assimp/code/Common/StandardShapes.cpp b/Engine/lib/assimp/code/Common/StandardShapes.cpp index 351f03f87..4a967997e 100644 --- a/Engine/lib/assimp/code/Common/StandardShapes.cpp +++ b/Engine/lib/assimp/code/Common/StandardShapes.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -421,16 +421,18 @@ void StandardShapes::MakeCone(ai_real height, ai_real radius1, positions.push_back(v3); if (!bOpen) { + const ai_real zero(0.0); + // generate the end 'cap' positions.emplace_back(s * radius2, halfHeight, t * radius2); positions.emplace_back(s2 * radius2, halfHeight, t2 * radius2); - positions.emplace_back(0.0, halfHeight, 0.0); + positions.emplace_back(zero, halfHeight, zero); if (radius1) { // generate the other end 'cap' positions.emplace_back(s * radius1, -halfHeight, t * radius1); positions.emplace_back(s2 * radius1, -halfHeight, t2 * radius1); - positions.emplace_back(0.0, -halfHeight, 0.0); + positions.emplace_back(zero, -halfHeight, zero); } } s = s2; @@ -466,13 +468,14 @@ void StandardShapes::MakeCircle(ai_real radius, unsigned int tess, ai_real t = 0.0; // std::sin(angle == 0); for (ai_real angle = 0.0; angle < angle_max;) { - positions.emplace_back(s * radius, 0.0, t * radius); + const ai_real zero(0.0); + positions.emplace_back(s * radius, zero, t * radius); angle += angle_delta; s = std::cos(angle); t = std::sin(angle); - positions.emplace_back(s * radius, 0.0, t * radius); + positions.emplace_back(s * radius, zero, t * radius); - positions.emplace_back(0.0, 0.0, 0.0); + positions.emplace_back(zero, zero, zero); } } diff --git a/Engine/lib/assimp/code/Common/StbCommon.h b/Engine/lib/assimp/code/Common/StbCommon.h index 1265d25da..aef23ce17 100644 --- a/Engine/lib/assimp/code/Common/StbCommon.h +++ b/Engine/lib/assimp/code/Common/StbCommon.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -48,6 +48,71 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma GCC diagnostic ignored "-Wunused-function" #endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifndef STB_USE_HUNTER +/* Use prefixed names for the symbols from stb_image as it is a very commonly embedded library. + Including vanilla stb_image symbols causes duplicate symbol problems if assimp is linked + statically together with another library or executable that also embeds stb_image. + + Symbols are not prefixed if using Hunter because in such case there exists a single true + stb_image library on the system that is used by assimp and can be used by all the other + libraries and executables. + + The list can be regenerated using the following: + + cat "path/to/stb/stb_image.h" | fgrep STBIDEF | fgrep '(' | sed -E 's/\*|\(.+//g' | \ + awk '{print "#define " $(NF) " assimp_" $(NF) }' | sort | uniq +*/ +#define stbi_convert_iphone_png_to_rgb assimp_stbi_convert_iphone_png_to_rgb +#define stbi_convert_iphone_png_to_rgb_thread assimp_stbi_convert_iphone_png_to_rgb_thread +#define stbi_convert_wchar_to_utf8 assimp_stbi_convert_wchar_to_utf8 +#define stbi_failure_reason assimp_stbi_failure_reason +#define stbi_hdr_to_ldr_gamma assimp_stbi_hdr_to_ldr_gamma +#define stbi_hdr_to_ldr_scale assimp_stbi_hdr_to_ldr_scale +#define stbi_image_free assimp_stbi_image_free +#define stbi_info assimp_stbi_info +#define stbi_info_from_callbacks assimp_stbi_info_from_callbacks +#define stbi_info_from_file assimp_stbi_info_from_file +#define stbi_info_from_memory assimp_stbi_info_from_memory +#define stbi_is_16_bit assimp_stbi_is_16_bit +#define stbi_is_16_bit_from_callbacks assimp_stbi_is_16_bit_from_callbacks +#define stbi_is_16_bit_from_file assimp_stbi_is_16_bit_from_file +#define stbi_is_16_bit_from_memory assimp_stbi_is_16_bit_from_memory +#define stbi_is_hdr assimp_stbi_is_hdr +#define stbi_is_hdr_from_callbacks assimp_stbi_is_hdr_from_callbacks +#define stbi_is_hdr_from_file assimp_stbi_is_hdr_from_file +#define stbi_is_hdr_from_memory assimp_stbi_is_hdr_from_memory +#define stbi_ldr_to_hdr_gamma assimp_stbi_ldr_to_hdr_gamma +#define stbi_ldr_to_hdr_scale assimp_stbi_ldr_to_hdr_scale +#define stbi_load assimp_stbi_load +#define stbi_load_16 assimp_stbi_load_16 +#define stbi_load_16_from_callbacks assimp_stbi_load_16_from_callbacks +#define stbi_load_16_from_memory assimp_stbi_load_16_from_memory +#define stbi_load_from_callbacks assimp_stbi_load_from_callbacks +#define stbi_load_from_file assimp_stbi_load_from_file +#define stbi_load_from_file_16 assimp_stbi_load_from_file_16 +#define stbi_load_from_memory assimp_stbi_load_from_memory +#define stbi_load_gif_from_memory assimp_stbi_load_gif_from_memory +#define stbi_loadf assimp_stbi_loadf +#define stbi_loadf_from_callbacks assimp_stbi_loadf_from_callbacks +#define stbi_loadf_from_file assimp_stbi_loadf_from_file +#define stbi_loadf_from_memory assimp_stbi_loadf_from_memory +#define stbi_set_flip_vertically_on_load assimp_stbi_set_flip_vertically_on_load +#define stbi_set_flip_vertically_on_load_thread assimp_stbi_set_flip_vertically_on_load_thread +#define stbi_set_unpremultiply_on_load assimp_stbi_set_unpremultiply_on_load +#define stbi_set_unpremultiply_on_load_thread assimp_stbi_set_unpremultiply_on_load_thread +#define stbi_zlib_decode_buffer assimp_stbi_zlib_decode_buffer +#define stbi_zlib_decode_malloc assimp_stbi_zlib_decode_malloc +#define stbi_zlib_decode_malloc_guesssize assimp_stbi_zlib_decode_malloc_guesssize +#define stbi_zlib_decode_malloc_guesssize_headerflag assimp_stbi_zlib_decode_malloc_guesssize_headerflag +#define stbi_zlib_decode_noheader_buffer assimp_stbi_zlib_decode_noheader_buffer +#define stbi_zlib_decode_noheader_malloc assimp_stbi_zlib_decode_noheader_malloc +#endif + #include "stb/stb_image.h" #if _MSC_VER @@ -56,3 +121,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma GCC diagnostic pop #endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif diff --git a/Engine/lib/assimp/code/Common/StdOStreamLogStream.h b/Engine/lib/assimp/code/Common/StdOStreamLogStream.h index cc0e06263..683c87d5f 100644 --- a/Engine/lib/assimp/code/Common/StdOStreamLogStream.h +++ b/Engine/lib/assimp/code/Common/StdOStreamLogStream.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team diff --git a/Engine/lib/assimp/code/Common/Subdivision.cpp b/Engine/lib/assimp/code/Common/Subdivision.cpp index 705ea3fb3..78c249807 100644 --- a/Engine/lib/assimp/code/Common/Subdivision.cpp +++ b/Engine/lib/assimp/code/Common/Subdivision.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -50,7 +49,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include + using namespace Assimp; + void mydummy() {} #ifdef _MSC_VER @@ -78,7 +80,7 @@ public: }; typedef std::vector UIntVector; - typedef std::map EdgeMap; + typedef std::unordered_map EdgeMap; // --------------------------------------------------------------------------- // Hashing function to derive an index into an #EdgeMap from two given @@ -522,7 +524,11 @@ void CatmullClarkSubdivider::InternSubdivide( } } - ai_assert(adj[o] - moffsets[nidx].first < mp->mNumFaces); + if (mp == nullptr) { + continue; + } + + ai_assert(adj[o] - moffsets[nidx].first < mp->mNumFaces); const aiFace &f = mp->mFaces[adj[o] - moffsets[nidx].first]; bool haveit = false; diff --git a/Engine/lib/assimp/code/Common/TargetAnimation.cpp b/Engine/lib/assimp/code/Common/TargetAnimation.cpp index 5f6d9bad0..9ef4e5d6c 100644 --- a/Engine/lib/assimp/code/Common/TargetAnimation.cpp +++ b/Engine/lib/assimp/code/Common/TargetAnimation.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/TargetAnimation.h b/Engine/lib/assimp/code/Common/TargetAnimation.h index 863406b94..116b55d2a 100644 --- a/Engine/lib/assimp/code/Common/TargetAnimation.h +++ b/Engine/lib/assimp/code/Common/TargetAnimation.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -119,12 +118,16 @@ private: * look-at target */ class TargetAnimationHelper { public: + /// @brief The class constructor. TargetAnimationHelper() : targetPositions(nullptr), objectPositions(nullptr) { // empty } + /// @brief The class destructor. + ~TargetAnimationHelper() = default; + // ------------------------------------------------------------------ /** Sets the target animation channel * diff --git a/Engine/lib/assimp/code/Common/Version.cpp b/Engine/lib/assimp/code/Common/Version.cpp index 808c3598d..2fca44824 100644 --- a/Engine/lib/assimp/code/Common/Version.cpp +++ b/Engine/lib/assimp/code/Common/Version.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -44,15 +44,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ScenePrivate.h" #include #include - -#include "revision.h" +#include // -------------------------------------------------------------------------------- // Legal information string - don't remove this. -static const char *LEGAL_INFORMATION = +static constexpr char LEGAL_INFORMATION[] = "Open Asset Import Library (Assimp).\n" "A free C/C++ library to import various 3D file formats into applications\n\n" - "(c) 2006-2022, Assimp team\n" + "(c) 2006-2024, Assimp team\n" "License under the terms and conditions of the 3-clause BSD license\n" "https://www.assimp.org\n"; @@ -118,73 +117,3 @@ ASSIMP_API const char *aiGetBranchName() { return GitBranch; } -// ------------------------------------------------------------------------------------------------ -ASSIMP_API aiScene::aiScene() : - mFlags(0), - mRootNode(nullptr), - mNumMeshes(0), - mMeshes(nullptr), - mNumMaterials(0), - mMaterials(nullptr), - mNumAnimations(0), - mAnimations(nullptr), - mNumTextures(0), - mTextures(nullptr), - mNumLights(0), - mLights(nullptr), - mNumCameras(0), - mCameras(nullptr), - mMetaData(nullptr), - mName(), - mNumSkeletons(0), - mSkeletons(nullptr), - mPrivate(new Assimp::ScenePrivateData()) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -ASSIMP_API aiScene::~aiScene() { - // delete all sub-objects recursively - delete mRootNode; - - // To make sure we won't crash if the data is invalid it's - // much better to check whether both mNumXXX and mXXX are - // valid instead of relying on just one of them. - if (mNumMeshes && mMeshes) - for (unsigned int a = 0; a < mNumMeshes; a++) - delete mMeshes[a]; - delete[] mMeshes; - - if (mNumMaterials && mMaterials) { - for (unsigned int a = 0; a < mNumMaterials; ++a) { - delete mMaterials[a]; - } - } - delete[] mMaterials; - - if (mNumAnimations && mAnimations) - for (unsigned int a = 0; a < mNumAnimations; a++) - delete mAnimations[a]; - delete[] mAnimations; - - if (mNumTextures && mTextures) - for (unsigned int a = 0; a < mNumTextures; a++) - delete mTextures[a]; - delete[] mTextures; - - if (mNumLights && mLights) - for (unsigned int a = 0; a < mNumLights; a++) - delete mLights[a]; - delete[] mLights; - - if (mNumCameras && mCameras) - for (unsigned int a = 0; a < mNumCameras; a++) - delete mCameras[a]; - delete[] mCameras; - - aiMetadata::Dealloc(mMetaData); - - delete[] mSkeletons; - - delete static_cast(mPrivate); -} diff --git a/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.cpp b/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.cpp index 555e3e386..616e7e797 100644 --- a/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.cpp +++ b/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.h b/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.h index 41d809450..20d3bd32c 100644 --- a/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.h +++ b/Engine/lib/assimp/code/Common/VertexTriangleAdjacency.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/Win32DebugLogStream.h b/Engine/lib/assimp/code/Common/Win32DebugLogStream.h index 34d849e83..f8bc017af 100644 --- a/Engine/lib/assimp/code/Common/Win32DebugLogStream.h +++ b/Engine/lib/assimp/code/Common/Win32DebugLogStream.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team diff --git a/Engine/lib/assimp/code/Common/ZipArchiveIOSystem.cpp b/Engine/lib/assimp/code/Common/ZipArchiveIOSystem.cpp index 3d5c72e27..23d7db15d 100644 --- a/Engine/lib/assimp/code/Common/ZipArchiveIOSystem.cpp +++ b/Engine/lib/assimp/code/Common/ZipArchiveIOSystem.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,13 +62,13 @@ namespace Assimp { // ---------------------------------------------------------------- // A read-only file inside a ZIP -class ZipFile : public IOStream { +class ZipFile final : public IOStream { friend class ZipFileInfo; explicit ZipFile(std::string &filename, size_t size); public: std::string m_Filename; - virtual ~ZipFile(); + ~ZipFile() override = default; // IOStream interface size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override; @@ -89,6 +89,8 @@ private: // Wraps an existing Assimp::IOSystem for unzip class IOSystem2Unzip { public: + IOSystem2Unzip() = default; + ~IOSystem2Unzip() = default; static voidpf open(voidpf opaque, const char *filename, int mode); static voidpf opendisk(voidpf opaque, voidpf stream, uint32_t number_disk, int mode); static uLong read(voidpf opaque, voidpf stream, void *buf, uLong size); @@ -100,6 +102,7 @@ public: static zlib_filefunc_def get(IOSystem *pIOHandler); }; +// ---------------------------------------------------------------- voidpf IOSystem2Unzip::open(voidpf opaque, const char *filename, int mode) { IOSystem *io_system = reinterpret_cast(opaque); @@ -119,9 +122,10 @@ voidpf IOSystem2Unzip::open(voidpf opaque, const char *filename, int mode) { return (voidpf)io_system->Open(filename, mode_fopen); } +// ---------------------------------------------------------------- voidpf IOSystem2Unzip::opendisk(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) { ZipFile *io_stream = (ZipFile *)stream; - voidpf ret = NULL; + voidpf ret = nullptr; int i; char *disk_filename = (char*)malloc(io_stream->m_Filename.length() + 1); @@ -141,24 +145,28 @@ voidpf IOSystem2Unzip::opendisk(voidpf opaque, voidpf stream, uint32_t number_di return ret; } +// ---------------------------------------------------------------- uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void *buf, uLong size) { IOStream *io_stream = (IOStream *)stream; return static_cast(io_stream->Read(buf, 1, size)); } +// ---------------------------------------------------------------- uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void *buf, uLong size) { IOStream *io_stream = (IOStream *)stream; return static_cast(io_stream->Write(buf, 1, size)); } +// ---------------------------------------------------------------- long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) { IOStream *io_stream = (IOStream *)stream; return static_cast(io_stream->Tell()); } +// ---------------------------------------------------------------- long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) { IOStream *io_stream = (IOStream *)stream; @@ -179,6 +187,7 @@ long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int or return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1); } +// ---------------------------------------------------------------- int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { IOSystem *io_system = (IOSystem *)opaque; IOStream *io_stream = (IOStream *)stream; @@ -188,10 +197,12 @@ int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { return 0; } +// ---------------------------------------------------------------- int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) { return 0; } +// ---------------------------------------------------------------- zlib_filefunc_def IOSystem2Unzip::get(IOSystem *pIOHandler) { zlib_filefunc_def mapping; @@ -213,9 +224,10 @@ zlib_filefunc_def IOSystem2Unzip::get(IOSystem *pIOHandler) { // ---------------------------------------------------------------- // Info about a read-only file inside a ZIP -class ZipFileInfo { +class ZipFileInfo final { public: explicit ZipFileInfo(unzFile zip_handle, size_t size); + ~ZipFileInfo() = default; // Allocate and Extract data from the ZIP ZipFile *Extract(std::string &filename, unzFile zip_handle) const; @@ -225,6 +237,7 @@ private: unz_file_pos_s m_ZipFilePos; }; +// ---------------------------------------------------------------- ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size) : m_Size(size) { ai_assert(m_Size != 0); @@ -234,6 +247,7 @@ ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size) : unzGetFilePos(zip_handle, &(m_ZipFilePos)); } +// ---------------------------------------------------------------- ZipFile *ZipFileInfo::Extract(std::string &filename, unzFile zip_handle) const { // Find in the ZIP. This cannot fail unz_file_pos_s *filepos = const_cast(&(m_ZipFilePos)); @@ -273,14 +287,14 @@ ZipFile *ZipFileInfo::Extract(std::string &filename, unzFile zip_handle) const { return zip_file; } +// ---------------------------------------------------------------- ZipFile::ZipFile(std::string &filename, size_t size) : m_Filename(filename), m_Size(size) { ai_assert(m_Size != 0); m_Buffer = std::unique_ptr(new uint8_t[m_Size]); } -ZipFile::~ZipFile() = default; - +// ---------------------------------------------------------------- size_t ZipFile::Read(void *pvBuffer, size_t pSize, size_t pCount) { // Should be impossible ai_assert(m_Buffer != nullptr); @@ -305,10 +319,12 @@ size_t ZipFile::Read(void *pvBuffer, size_t pSize, size_t pCount) { return pCount; } +// ---------------------------------------------------------------- size_t ZipFile::FileSize() const { return m_Size; } +// ---------------------------------------------------------------- aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) { switch (pOrigin) { case aiOrigin_SET: { @@ -334,6 +350,7 @@ aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) { return aiReturn_FAILURE; } +// ---------------------------------------------------------------- size_t ZipFile::Tell() const { return m_SeekPtr; } @@ -365,6 +382,7 @@ private: ZipFileInfoMap m_ArchiveMap; }; +// ---------------------------------------------------------------- ZipArchiveIOSystem::Implement::Implement(IOSystem *pIOHandler, const char *pFilename, const char *pMode) { ai_assert(strcmp(pMode, "r") == 0); ai_assert(pFilename != nullptr); @@ -376,12 +394,14 @@ ZipArchiveIOSystem::Implement::Implement(IOSystem *pIOHandler, const char *pFile m_ZipFileHandle = unzOpen2(pFilename, &mapping); } +// ---------------------------------------------------------------- ZipArchiveIOSystem::Implement::~Implement() { if (m_ZipFileHandle != nullptr) { unzClose(m_ZipFileHandle); } } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::Implement::MapArchive() { if (m_ZipFileHandle == nullptr) return; @@ -408,10 +428,12 @@ void ZipArchiveIOSystem::Implement::MapArchive() { } while (unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE); } +// ---------------------------------------------------------------- bool ZipArchiveIOSystem::Implement::isOpen() const { return (m_ZipFileHandle != nullptr); } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::Implement::getFileList(std::vector &rFileList) { MapArchive(); rFileList.clear(); @@ -421,6 +443,7 @@ void ZipArchiveIOSystem::Implement::getFileList(std::vector &rFileL } } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector &rFileList, const std::string &extension) { MapArchive(); rFileList.clear(); @@ -431,6 +454,7 @@ void ZipArchiveIOSystem::Implement::getFileListExtension(std::vectorExists(filename); } +// ---------------------------------------------------------------- // This is always '/' in a ZIP char ZipArchiveIOSystem::getOsSeparator() const { return '/'; } +// ---------------------------------------------------------------- // Only supports Reading IOStream *ZipArchiveIOSystem::Open(const char *pFilename, const char *pMode) { ai_assert(pFilename != nullptr); @@ -536,22 +569,27 @@ IOStream *ZipArchiveIOSystem::Open(const char *pFilename, const char *pMode) { return pImpl->OpenFile(filename); } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::Close(IOStream *pFile) { delete pFile; } +// ---------------------------------------------------------------- bool ZipArchiveIOSystem::isOpen() const { return (pImpl->isOpen()); } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::getFileList(std::vector &rFileList) const { return pImpl->getFileList(rFileList); } +// ---------------------------------------------------------------- void ZipArchiveIOSystem::getFileListExtension(std::vector &rFileList, const std::string &extension) const { return pImpl->getFileListExtension(rFileList, extension); } +// ---------------------------------------------------------------- bool ZipArchiveIOSystem::isZipArchive(IOSystem *pIOHandler, const char *pFilename) { Implement tmp(pIOHandler, pFilename, "r"); return tmp.isOpen(); diff --git a/Engine/lib/assimp/code/Common/material.cpp b/Engine/lib/assimp/code/Common/material.cpp index ffc2f415b..76676ec1a 100644 --- a/Engine/lib/assimp/code/Common/material.cpp +++ b/Engine/lib/assimp/code/Common/material.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/scene.cpp b/Engine/lib/assimp/code/Common/scene.cpp index b0e882154..5c2e370d2 100644 --- a/Engine/lib/assimp/code/Common/scene.cpp +++ b/Engine/lib/assimp/code/Common/scene.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,6 +40,87 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include "ScenePrivate.h" + +aiScene::aiScene() : + mFlags(0), + mRootNode(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mNumMaterials(0), + mMaterials(nullptr), + mNumAnimations(0), + mAnimations(nullptr), + mNumTextures(0), + mTextures(nullptr), + mNumLights(0), + mLights(nullptr), + mNumCameras(0), + mCameras(nullptr), + mMetaData(nullptr), + mName(), + mNumSkeletons(0), + mSkeletons(nullptr), + mPrivate(new Assimp::ScenePrivateData()) { + // empty +} + +aiScene::~aiScene() { + // delete all sub-objects recursively + delete mRootNode; + + // To make sure we won't crash if the data is invalid it's + // much better to check whether both mNumXXX and mXXX are + // valid instead of relying on just one of them. + if (mNumMeshes && mMeshes) { + for (unsigned int a = 0; a < mNumMeshes; ++a) { + delete mMeshes[a]; + } + } + delete[] mMeshes; + + if (mNumMaterials && mMaterials) { + for (unsigned int a = 0; a < mNumMaterials; ++a) { + delete mMaterials[a]; + } + } + delete[] mMaterials; + + if (mNumAnimations && mAnimations) { + for (unsigned int a = 0; a < mNumAnimations; ++a) { + delete mAnimations[a]; + } + } + delete[] mAnimations; + + if (mNumTextures && mTextures) { + for (unsigned int a = 0; a < mNumTextures; ++a) { + delete mTextures[a]; + } + } + delete[] mTextures; + + if (mNumLights && mLights) { + for (unsigned int a = 0; a < mNumLights; ++a) { + delete mLights[a]; + } + } + delete[] mLights; + + if (mNumCameras && mCameras) { + for (unsigned int a = 0; a < mNumCameras; ++a) { + delete mCameras[a]; + } + } + delete[] mCameras; + + aiMetadata::Dealloc(mMetaData); + + delete[] mSkeletons; + + delete static_cast(mPrivate); +} + aiNode::aiNode() : mName(""), mParent(nullptr), diff --git a/Engine/lib/assimp/code/Common/simd.cpp b/Engine/lib/assimp/code/Common/simd.cpp index 0dd437d26..6a48750b0 100644 --- a/Engine/lib/assimp/code/Common/simd.cpp +++ b/Engine/lib/assimp/code/Common/simd.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Common/simd.h b/Engine/lib/assimp/code/Common/simd.h index a1d936629..05d27d253 100644 --- a/Engine/lib/assimp/code/Common/simd.h +++ b/Engine/lib/assimp/code/Common/simd.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Geometry/GeometryUtils.cpp b/Engine/lib/assimp/code/Geometry/GeometryUtils.cpp new file mode 100644 index 000000000..375e501d3 --- /dev/null +++ b/Engine/lib/assimp/code/Geometry/GeometryUtils.cpp @@ -0,0 +1,103 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#include "GeometryUtils.h" + +#include + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +ai_real GeometryUtils::heron( ai_real a, ai_real b, ai_real c ) { + const ai_real s = (a + b + c) / 2; + const ai_real area = pow((s * ( s - a ) * ( s - b ) * ( s - c ) ), (ai_real)0.5 ); + return area; +} + +// ------------------------------------------------------------------------------------------------ +ai_real GeometryUtils::distance3D( const aiVector3D &vA, const aiVector3D &vB ) { + const ai_real lx = ( vB.x - vA.x ); + const ai_real ly = ( vB.y - vA.y ); + const ai_real lz = ( vB.z - vA.z ); + const ai_real a = lx*lx + ly*ly + lz*lz; + const ai_real d = pow( a, (ai_real)0.5 ); + + return d; +} + +// ------------------------------------------------------------------------------------------------ +ai_real GeometryUtils::calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) { + ai_real area = 0; + + const aiVector3D vA( mesh->mVertices[ face.mIndices[ 0 ] ] ); + const aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] ); + const aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] ); + + const ai_real a = distance3D( vA, vB ); + const ai_real b = distance3D( vB, vC ); + const ai_real c = distance3D( vC, vA ); + area = heron( a, b, c ); + + return area; +} + +// ------------------------------------------------------------------------------------------------ +// Check whether a ray intersects a plane and find the intersection point +bool GeometryUtils::PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, + const aiVector3D& planeNormal, aiVector3D& pos) { + const ai_real b = planeNormal * (planePos - ray.pos); + ai_real h = ray.dir * planeNormal; + if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0) + return false; + + pos = ray.pos + (ray.dir * h); + return true; +} + +// ------------------------------------------------------------------------------------------------ +void GeometryUtils::normalizeVectorArray(aiVector3D *vectorArrayIn, aiVector3D *vectorArrayOut, + size_t numVectors) { + for (size_t i=0; i +#include + +namespace Assimp { + +// --------------------------------------------------------------------------- +/// @brief This helper class supports some basic geometry algorithms. +// --------------------------------------------------------------------------- +class ASSIMP_API GeometryUtils { +public: + /// @brief Will calculate the area of a triangle. + /// @param a The first vertex of the triangle. + /// @param b The first vertex of the triangle. + /// @param c The first vertex of the triangle. + static ai_real heron( ai_real a, ai_real b, ai_real c ); + + /// @brief Will compute the distance between 2 3D-vectors + /// @param vA Vector a. + /// @param vB Vector b. + /// @return The distance. + static ai_real distance3D( const aiVector3D &vA, const aiVector3D &vB ); + + /// @brief Will calculate the area of a triangle described by a aiFace. + /// @param face The face + /// @param mesh The mesh containing the face + /// @return The area. + static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ); + + /// @brief Will calculate the intersection between a ray and a plane + /// @param ray The ray to test for + /// @param planePos A point on the plane + /// @param planeNormal The plane normal to describe its orientation + /// @param pos The position of the intersection. + /// @return true is an intersection was detected, false if not. + static bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, const aiVector3D& planeNormal, aiVector3D& pos); + + /// @brief Will normalize an array of vectors. + /// @param vectorArrayIn The incoming arra of vectors. + /// @param vectorArrayOut The normalized vectors. + /// @param numVectors The array size. + static void normalizeVectorArray(aiVector3D *vectorArrayIn, aiVector3D *vectorArrayOut, size_t numVectors); +}; + +} // namespace Assimp diff --git a/Engine/lib/assimp/code/Material/MaterialSystem.cpp b/Engine/lib/assimp/code/Material/MaterialSystem.cpp index b2f738959..98318960a 100644 --- a/Engine/lib/assimp/code/Material/MaterialSystem.cpp +++ b/Engine/lib/assimp/code/Material/MaterialSystem.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include using namespace Assimp; @@ -73,7 +73,7 @@ aiReturn aiGetMaterialProperty(const aiMaterial *pMat, aiMaterialProperty *prop = pMat->mProperties[i]; if (prop /* just for safety ... */ - && 0 == strcmp(prop->mKey.data, pKey) && (UINT_MAX == type || prop->mSemantic == type) /* UINT_MAX is a wild-card, but this is undocumented :-) */ + && 0 == strncmp(prop->mKey.data, pKey, strlen(pKey)) && (UINT_MAX == type || prop->mSemantic == type) /* UINT_MAX is a wild-card, but this is undocumented :-) */ && (UINT_MAX == index || prop->mIndex == index)) { *pPropOut = pMat->mProperties[i]; return AI_SUCCESS; @@ -173,6 +173,95 @@ aiReturn aiGetMaterialFloatArray(const aiMaterial *pMat, return AI_SUCCESS; } +// ------------------------------------------------------------------------------------------------ +// Get an array of floating-point values from the material. +aiReturn aiGetMaterialDoubleArray(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + double *pOut, + unsigned int *pMax) { + ai_assert(pOut != nullptr); + ai_assert(pMat != nullptr); + + const aiMaterialProperty *prop; + aiGetMaterialProperty(pMat, pKey, type, index, (const aiMaterialProperty **)&prop); + if (nullptr == prop) { + return AI_FAILURE; + } + + // data is given in floats, convert to ai_real + unsigned int iWrite = 0; + if (aiPTI_Float == prop->mType || aiPTI_Buffer == prop->mType) { + iWrite = prop->mDataLength / sizeof(float); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + + if (pMax) { + *pMax = iWrite; + } + } + // data is given in doubles, convert to float + else if (aiPTI_Double == prop->mType) { + iWrite = prop->mDataLength / sizeof(double); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // data is given in ints, convert to float + else if (aiPTI_Integer == prop->mType) { + iWrite = prop->mDataLength / sizeof(int32_t); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // a string ... read floats separated by spaces + else { + if (pMax) { + iWrite = *pMax; + } + // strings are zero-terminated with a 32 bit length prefix, so this is safe + const char *cur = prop->mData + 4; + ai_assert(prop->mDataLength >= 5); + ai_assert(!prop->mData[prop->mDataLength - 1]); + for (unsigned int a = 0;; ++a) { + cur = fast_atoreal_move(cur, pOut[a]); + if (a == iWrite - 1) { + break; + } + if (!IsSpace(*cur)) { + ASSIMP_LOG_ERROR("Material property", pKey, + " is a string; failed to parse a float array out of it."); + return AI_FAILURE; + } + } + + if (pMax) { + *pMax = iWrite; + } + } + return AI_SUCCESS; +} + // ------------------------------------------------------------------------------------------------ // Get an array if integers from the material aiReturn aiGetMaterialIntegerArray(const aiMaterial *pMat, @@ -473,7 +562,7 @@ aiReturn aiMaterial::AddBinaryProperty(const void *pInput, } // Allocate a new material property - aiMaterialProperty *pcNew = new aiMaterialProperty(); + std::unique_ptr pcNew(new aiMaterialProperty()); // .. and fill it pcNew->mType = pType; @@ -485,11 +574,11 @@ aiReturn aiMaterial::AddBinaryProperty(const void *pInput, memcpy(pcNew->mData, pInput, pSizeInBytes); pcNew->mKey.length = static_cast(::strlen(pKey)); - ai_assert(MAXLEN > pcNew->mKey.length); + ai_assert(AI_MAXLEN > pcNew->mKey.length); strcpy(pcNew->mKey.data, pKey); if (UINT_MAX != iOutIndex) { - mProperties[iOutIndex] = pcNew; + mProperties[iOutIndex] = pcNew.release(); return AI_SUCCESS; } @@ -502,7 +591,6 @@ aiReturn aiMaterial::AddBinaryProperty(const void *pInput, try { ppTemp = new aiMaterialProperty *[mNumAllocated]; } catch (std::bad_alloc &) { - delete pcNew; return AI_OUTOFMEMORY; } @@ -513,7 +601,7 @@ aiReturn aiMaterial::AddBinaryProperty(const void *pInput, mProperties = ppTemp; } // push back ... - mProperties[mNumProperties++] = pcNew; + mProperties[mNumProperties++] = pcNew.release(); return AI_SUCCESS; } diff --git a/Engine/lib/assimp/code/Material/MaterialSystem.h b/Engine/lib/assimp/code/Material/MaterialSystem.h index 41891ad97..e7c752179 100644 --- a/Engine/lib/assimp/code/Material/MaterialSystem.h +++ b/Engine/lib/assimp/code/Material/MaterialSystem.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/Pbrt/PbrtExporter.cpp b/Engine/lib/assimp/code/Pbrt/PbrtExporter.cpp index a9f8656a4..d5d805868 100644 --- a/Engine/lib/assimp/code/Pbrt/PbrtExporter.cpp +++ b/Engine/lib/assimp/code/Pbrt/PbrtExporter.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -89,33 +89,53 @@ using namespace Assimp; namespace Assimp { -void ExportScenePbrt ( - const char* pFile, - IOSystem* pIOSystem, - const aiScene* pScene, - const ExportProperties* /*pProperties*/ -){ +void ExportScenePbrt(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, + const ExportProperties *) { std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); - + path = path + file + ".pbrt"; // initialize the exporter PbrtExporter exporter(pScene, pIOSystem, path, file); } } // end of namespace Assimp -// Constructor +static void create_embedded_textures_folder(const aiScene *scene, IOSystem *pIOSystem) { + if (scene->mNumTextures > 0) { + if (!pIOSystem->Exists("textures")) { + if (!pIOSystem->CreateDirectory("textures")) { + throw DeadlyExportError("Could not create textures/ directory."); + } + } + } +} + PbrtExporter::PbrtExporter( const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file) : mScene(pScene), mIOSystem(pIOSystem), mPath(path), - mFile(file) { + mFile(file), + mRootTransform( + // rotates the (already left-handed) CRS -90 degrees around the x axis in order to + // make +Z 'up' and +Y 'towards viewer', as in default in pbrt + 1.f, 0.f, 0.f, 0.f, // + 0.f, 0.f, -1.f, 0.f, // + 0.f, 1.f, 0.f, 0.f, // + 0.f, 0.f, 0.f, 1.f // + ) { + + mRootTransform = aiMatrix4x4( + -1.f, 0, 0.f, 0.f, // + 0.0f, -1.f, 0.f, 0.f, // + 0.f, 0.f, 1.f, 0.f, // + 0.f, 0.f, 0.f, 1.f // + ) * mRootTransform; + // Export embedded textures. - if (mScene->mNumTextures > 0) - if (!mIOSystem->CreateDirectory("textures")) - throw DeadlyExportError("Could not create textures/ directory."); + create_embedded_textures_folder(mScene, mIOSystem); + for (unsigned int i = 0; i < mScene->mNumTextures; ++i) { aiTexture* tex = mScene->mTextures[i]; std::string fn = CleanTextureFilename(tex->mFilename, false); @@ -161,9 +181,6 @@ PbrtExporter::PbrtExporter( outfile->Write(mOutput.str().c_str(), mOutput.str().length(), 1); } -// Destructor -PbrtExporter::~PbrtExporter() = default; - void PbrtExporter::WriteMetaData() { mOutput << "#############################\n"; mOutput << "# Scene metadata:\n"; @@ -260,7 +277,7 @@ aiMatrix4x4 PbrtExporter::GetNodeTransform(const aiString &name) const { node = node->mParent; } } - return m; + return mRootTransform * m; } std::string PbrtExporter::TransformAsString(const aiMatrix4x4 &m) { @@ -309,7 +326,7 @@ void PbrtExporter::WriteCamera(int i) { // Get camera fov float hfov = AI_RAD_TO_DEG(camera->mHorizontalFOV); - float fov = (aspect >= 1.0) ? hfov : (hfov * aspect); + float fov = (aspect >= 1.0) ? hfov : (hfov / aspect); if (fov < 5) { std::cerr << fov << ": suspiciously low field of view specified by camera. Setting to 45 degrees.\n"; fov = 45; @@ -327,7 +344,7 @@ void PbrtExporter::WriteCamera(int i) { if (!cameraActive) mOutput << "# "; - mOutput << "Scale -1 1 1\n"; // right handed -> left handed + mOutput << "Scale 1 1 1\n"; if (!cameraActive) mOutput << "# "; mOutput << "LookAt " @@ -383,8 +400,8 @@ void PbrtExporter::WriteWorldDefinition() { } mOutput << "# Geometry\n\n"; - aiMatrix4x4 worldFromObject; - WriteGeometricObjects(mScene->mRootNode, worldFromObject, meshUses); + + WriteGeometricObjects(mScene->mRootNode, mRootTransform, meshUses); } void PbrtExporter::WriteTextures() { @@ -783,10 +800,20 @@ void PbrtExporter::WriteLights() { void PbrtExporter::WriteMesh(aiMesh* mesh) { mOutput << "# - Mesh: "; + const char* mName; if (mesh->mName == aiString("")) - mOutput << "\n"; + mName = ""; else - mOutput << mesh->mName.C_Str() << "\n"; + mName = mesh->mName.C_Str(); + mOutput << mName << "\n"; + + // Check if any types other than tri + if ( (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) + || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) + || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { + std::cerr << "Error: ignoring point / line / polygon mesh " << mName << ".\n"; + return; + } mOutput << "AttributeBegin\n"; aiMaterial* material = mScene->mMaterials[mesh->mMaterialIndex]; @@ -799,14 +826,6 @@ void PbrtExporter::WriteMesh(aiMesh* mesh) { mOutput << " AreaLightSource \"diffuse\" \"rgb L\" [ " << emission.r << " " << emission.g << " " << emission.b << " ]\n"; - // Check if any types other than tri - if ( (mesh->mPrimitiveTypes & aiPrimitiveType_POINT) - || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) - || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { - std::cerr << "Error: ignoring point / line / polygon mesh " << mesh->mName.C_Str() << ".\n"; - return; - } - // Alpha mask std::string alpha; aiString opacityTexture; diff --git a/Engine/lib/assimp/code/Pbrt/PbrtExporter.h b/Engine/lib/assimp/code/Pbrt/PbrtExporter.h index 4f4e1625d..0242ddcf0 100644 --- a/Engine/lib/assimp/code/Pbrt/PbrtExporter.h +++ b/Engine/lib/assimp/code/Pbrt/PbrtExporter.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -70,15 +70,33 @@ class ExportProperties; // --------------------------------------------------------------------- /** Helper class to export a given scene to a Pbrt file. */ // --------------------------------------------------------------------- -class PbrtExporter -{ +class PbrtExporter { public: /// Constructor for a specific scene to export PbrtExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file); /// Destructor - virtual ~PbrtExporter(); + virtual ~PbrtExporter() = default; + +private: + aiMatrix4x4 GetNodeTransform(const aiString &name) const; + static std::string TransformAsString(const aiMatrix4x4 &m); + static std::string RemoveSuffix(std::string filename); + std::string CleanTextureFilename(const aiString &f, bool rewriteExtension = true) const; + void WriteMetaData(); + void WriteWorldDefinition(); + void WriteCameras(); + void WriteCamera(int i); + void WriteLights(); + void WriteTextures(); + static bool TextureHasAlphaMask(const std::string &filename); + void WriteMaterials(); + void WriteMaterial(int i); + void WriteMesh(aiMesh *mesh); + void WriteInstanceDefinition(int i); + void WriteGeometricObjects(aiNode *node, aiMatrix4x4 parentTransform, + std::map &meshUses); private: // the scene to export @@ -96,36 +114,11 @@ private: /// Name of the file (without extension) where the scene will be exported const std::string mFile; -private: // A private set to keep track of which textures have been declared std::set mTextureSet; - aiMatrix4x4 GetNodeTransform(const aiString& name) const; - static std::string TransformAsString(const aiMatrix4x4& m); - - static std::string RemoveSuffix(std::string filename); - std::string CleanTextureFilename(const aiString &f, bool rewriteExtension = true) const; - - void WriteMetaData(); - - void WriteWorldDefinition(); - - void WriteCameras(); - void WriteCamera(int i); - - void WriteLights(); - - void WriteTextures(); - static bool TextureHasAlphaMask(const std::string &filename); - - void WriteMaterials(); - void WriteMaterial(int i); - - void WriteMesh(aiMesh* mesh); - - void WriteInstanceDefinition(int i); - void WriteGeometricObjects(aiNode* node, aiMatrix4x4 parentTransform, - std::map &meshUses); + // Transform to apply to the root node and all root objects such as cameras, lights, etc. + aiMatrix4x4 mRootTransform; }; } // namespace Assimp diff --git a/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.cpp b/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.cpp index a05cd91e9..fa524a7fd 100644 --- a/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,15 +43,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include namespace Assimp { -/// The default class constructor. -ArmaturePopulate::ArmaturePopulate() = default; +static bool IsBoneNode(const aiString &bone_name, std::vector &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } -/// The class destructor. -ArmaturePopulate::~ArmaturePopulate() = default; + return false; +} bool ArmaturePopulate::IsActive(unsigned int pFlags) const { return (pFlags & aiProcess_PopulateArmatureData) != 0; @@ -70,7 +73,7 @@ void ArmaturePopulate::Execute(aiScene *out) { BuildBoneList(out->mRootNode, out->mRootNode, out, bones); BuildNodeList(out->mRootNode, nodes); - BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + BuildBoneStack(out->mRootNode, out, bones, bone_stack, nodes); ASSIMP_LOG_DEBUG("Bone stack size: ", bone_stack.size()); @@ -78,9 +81,8 @@ void ArmaturePopulate::Execute(aiScene *out) { aiBone *bone = kvp.first; aiNode *bone_node = kvp.second; ASSIMP_LOG_VERBOSE_DEBUG("active node lookup: ", bone->mName.C_Str()); + // lcl transform grab - done in generate_nodes :) - - // bone->mOffsetMatrix = bone_node->mTransformation; aiNode *armature = GetArmatureRoot(bone_node, bones); ai_assert(armature); @@ -159,8 +161,7 @@ void ArmaturePopulate::BuildNodeList(const aiNode *current_node, // A bone stack allows us to have multiple armatures, with the same bone names // A bone stack allows us also to retrieve bones true transform even with // duplicate names :) -void ArmaturePopulate::BuildBoneStack(aiNode *, - const aiNode *root_node, +void ArmaturePopulate::BuildBoneStack(const aiNode *root_node, const aiScene*, const std::vector &bones, std::map &bone_stack, @@ -196,8 +197,7 @@ void ArmaturePopulate::BuildBoneStack(aiNode *, // This is required to be detected for a bone initially, it will recurse up // until it cannot find another bone and return the node No known failure // points. (yet) -aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, - std::vector &bone_list) { +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, std::vector &bone_list) { while (nullptr != bone_node) { if (!IsBoneNode(bone_node->mName, bone_list)) { ASSIMP_LOG_VERBOSE_DEBUG("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str()); @@ -212,18 +212,6 @@ aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, return nullptr; } -// Simple IsBoneNode check if this could be a bone -bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, - std::vector &bones) { - for (aiBone *bone : bones) { - if (bone->mName == bone_name) { - return true; - } - } - - return false; -} - // Pop this node by name from the stack if found // Used in multiple armature situations with duplicate node / bone names // Known flaw: cannot have nodes with bone names, will be fixed in later release diff --git a/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.h b/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.h index 530932f48..af1792fb0 100644 --- a/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.h +++ b/Engine/lib/assimp/code/PostProcessing/ArmaturePopulate.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -69,10 +69,10 @@ namespace Assimp { class ASSIMP_API ArmaturePopulate : public BaseProcess { public: /// The default class constructor. - ArmaturePopulate(); + ArmaturePopulate() = default; /// The class destructor. - virtual ~ArmaturePopulate(); + virtual ~ArmaturePopulate() = default; /// Overwritten, @see BaseProcess virtual bool IsActive( unsigned int pFlags ) const; @@ -86,9 +86,6 @@ public: static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector &bone_list); - static bool IsBoneNode(const aiString &bone_name, - std::vector &bones); - static aiNode *GetNodeFromStack(const aiString &node_name, std::vector &nodes); @@ -99,7 +96,7 @@ public: const aiScene *scene, std::vector &bones); - static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, + static void BuildBoneStack(const aiNode *root_node, const aiScene *scene, const std::vector &bones, std::map &bone_stack, @@ -108,5 +105,4 @@ public: } // Namespace Assimp - #endif // SCALE_PROCESS_H_ diff --git a/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.cpp b/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.cpp index efc457766..8fd063260 100644 --- a/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -60,10 +60,6 @@ CalcTangentsProcess::CalcTangentsProcess() : // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -CalcTangentsProcess::~CalcTangentsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool CalcTangentsProcess::IsActive(unsigned int pFlags) const { @@ -189,9 +185,9 @@ bool CalcTangentsProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshIndex) { tangent.x = (w.x * sy - v.x * ty) * dirCorrection; tangent.y = (w.y * sy - v.y * ty) * dirCorrection; tangent.z = (w.z * sy - v.z * ty) * dirCorrection; - bitangent.x = (- w.x * sx + v.x * tx) * dirCorrection; - bitangent.y = (- w.y * sx + v.y * tx) * dirCorrection; - bitangent.z = (- w.z * sx + v.z * tx) * dirCorrection; + bitangent.x = (w.x * sx - v.x * tx) * dirCorrection; + bitangent.y = (w.y * sx - v.y * tx) * dirCorrection; + bitangent.z = (w.z * sx - v.z * tx) * dirCorrection; // store for every vertex of that face for (unsigned int b = 0; b < face.mNumIndices; ++b) { @@ -199,13 +195,15 @@ bool CalcTangentsProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshIndex) { // project tangent and bitangent into the plane formed by the vertex' normal aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]); - aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]) - localTangent * (bitangent * localTangent); + aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]); localTangent.NormalizeSafe(); localBitangent.NormalizeSafe(); // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN. - bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z); - bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z); + bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z) + || (-0.5f < localTangent.x && localTangent.x < 0.5f && -0.5f < localTangent.y && localTangent.y < 0.5f && -0.5f < localTangent.z && localTangent.z < 0.5f); + bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z) + || (-0.5f < localBitangent.x && localBitangent.x < 0.5f && -0.5f < localBitangent.y && localBitangent.y < 0.5f && -0.5f < localBitangent.z && localBitangent.z < 0.5f); if (invalid_tangent != invalid_bitangent) { if (invalid_tangent) { localTangent = meshNorm[p] ^ localBitangent; diff --git a/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.h b/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.h index 018789bae..3d7bb2a5e 100644 --- a/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/CalcTangentsProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,14 +59,11 @@ namespace Assimp * because the joining of vertices also considers tangents and bitangents for * uniqueness. */ -class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess -{ +class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess { public: - CalcTangentsProcess(); - ~CalcTangentsProcess(); + ~CalcTangentsProcess() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. * @param pFlags The processing flags the importer was called with. @@ -74,24 +71,21 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; // setter for configMaxAngle - inline void SetMaxSmoothAngle(float f) - { + void SetMaxSmoothAngle(float f) { configMaxAngle =f; } protected: - // ------------------------------------------------------------------- /** Calculates tangents and bitangents for a specific mesh. * @param pMesh The mesh to process. @@ -103,10 +97,9 @@ protected: /** Executes the post processing step on the given imported data. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; private: - /** Configuration option: maximum smoothing angle, in radians*/ float configMaxAngle; unsigned int configSourceUV; diff --git a/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp b/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp index 237409f02..cac51e8d0 100644 --- a/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -42,8 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file GenUVCoords step */ - #include "ComputeUVMappingProcess.h" +#include "Geometry/GeometryUtils.h" #include "ProcessHelper.h" #include @@ -51,47 +50,25 @@ using namespace Assimp; namespace { - const static aiVector3D base_axis_y(0.0,1.0,0.0); - const static aiVector3D base_axis_x(1.0,0.0,0.0); - const static aiVector3D base_axis_z(0.0,0.0,1.0); - const static ai_real angle_epsilon = ai_real( 0.95 ); -} - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -ComputeUVMappingProcess::ComputeUVMappingProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ComputeUVMappingProcess::~ComputeUVMappingProcess() = default; +const static aiVector3D base_axis_y(0.0, 1.0, 0.0); +const static aiVector3D base_axis_x(1.0, 0.0, 0.0); +const static aiVector3D base_axis_z(0.0, 0.0, 1.0); +const static ai_real angle_epsilon = ai_real(0.95); +} // namespace // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const -{ - return (pFlags & aiProcess_GenUVCoords) != 0; -} - -// ------------------------------------------------------------------------------------------------ -// Check whether a ray intersects a plane and find the intersection point -inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, - const aiVector3D& planeNormal, aiVector3D& pos) -{ - const ai_real b = planeNormal * (planePos - ray.pos); - ai_real h = ray.dir * planeNormal; - if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0) - return false; - - pos = ray.pos + (ray.dir * h); - return true; +bool ComputeUVMappingProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_GenUVCoords) != 0; } // ------------------------------------------------------------------------------------------------ // Find the first empty UV channel in a mesh -inline unsigned int FindEmptyUVChannel (aiMesh* mesh) -{ - for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m) - if (!mesh->mTextureCoords[m])return m; +inline unsigned int FindEmptyUVChannel(aiMesh *mesh) { + for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++m) + if (!mesh->mTextureCoords[m]) { + return m; + } ASSIMP_LOG_ERROR("Unable to compute UV coordinates, no free UV slot found"); return UINT_MAX; @@ -99,22 +76,22 @@ inline unsigned int FindEmptyUVChannel (aiMesh* mesh) // ------------------------------------------------------------------------------------------------ // Try to remove UV seams -void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) -{ +void RemoveUVSeams(aiMesh *mesh, aiVector3D *out) { // TODO: just a very rough algorithm. I think it could be done // much easier, but I don't know how and am currently too tired to // to think about a better solution. - const static ai_real LOWER_LIMIT = ai_real( 0.1 ); - const static ai_real UPPER_LIMIT = ai_real( 0.9 ); + const static ai_real LOWER_LIMIT = ai_real(0.1); + const static ai_real UPPER_LIMIT = ai_real(0.9); - const static ai_real LOWER_EPSILON = ai_real( 10e-3 ); - const static ai_real UPPER_EPSILON = ai_real( 1.0-10e-3 ); + const static ai_real LOWER_EPSILON = ai_real(10e-3); + const static ai_real UPPER_EPSILON = ai_real(1.0 - 10e-3); - for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx) - { - const aiFace& face = mesh->mFaces[fidx]; - if (face.mNumIndices < 3) continue; // triangles and polygons only, please + for (unsigned int fidx = 0; fidx < mesh->mNumFaces; ++fidx) { + const aiFace &face = mesh->mFaces[fidx]; + if (face.mNumIndices < 3) { + continue; // triangles and polygons only, please + } unsigned int smallV = face.mNumIndices, large = smallV; bool zero = false, one = false, round_to_zero = false; @@ -123,20 +100,18 @@ void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) // but the assumption that a face with at least one very small // on the one side and one very large U coord on the other side // lies on a UV seam should work for most cases. - for (unsigned int n = 0; n < face.mNumIndices;++n) - { - if (out[face.mIndices[n]].x < LOWER_LIMIT) - { + for (unsigned int n = 0; n < face.mNumIndices; ++n) { + if (out[face.mIndices[n]].x < LOWER_LIMIT) { smallV = n; // If we have a U value very close to 0 we can't // round the others to 0, too. if (out[face.mIndices[n]].x <= LOWER_EPSILON) zero = true; - else round_to_zero = true; + else + round_to_zero = true; } - if (out[face.mIndices[n]].x > UPPER_LIMIT) - { + if (out[face.mIndices[n]].x > UPPER_LIMIT) { large = n; // If we have a U value very close to 1 we can't @@ -145,10 +120,8 @@ void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) one = true; } } - if (smallV != face.mNumIndices && large != face.mNumIndices) - { - for (unsigned int n = 0; n < face.mNumIndices;++n) - { + if (smallV != face.mNumIndices && large != face.mNumIndices) { + for (unsigned int n = 0; n < face.mNumIndices; ++n) { // If the u value is over the upper limit and no other u // value of that face is 0, round it to 0 if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero) @@ -164,9 +137,8 @@ void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) // Due to numerical inaccuracies one U coord becomes 0, the // other 1. But we do still have a third UV coord to determine // to which side we must round to. - else if (one && zero) - { - if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON) + else if (one && zero) { + if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON) out[face.mIndices[n]].x = 0.0; else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON) out[face.mIndices[n]].x = 1.0; @@ -177,8 +149,7 @@ void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) } // ------------------------------------------------------------------------------------------------ -void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) -{ +void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh *mesh, const aiVector3D &axis, aiVector3D *out) { aiVector3D center, min, max; FindMeshCenter(mesh, center, min, max); @@ -186,7 +157,7 @@ void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D // currently the mapping axis will always be one of x,y,z, except if the // PretransformVertices step is used (it transforms the meshes into worldspace, // thus changing the mapping axis) - if (axis * base_axis_x >= angle_epsilon) { + if (axis * base_axis_x >= angle_epsilon) { // For each point get a normalized projection vector in the sphere, // get its longitude and latitude and map them to their respective @@ -200,58 +171,54 @@ void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D // Thus we can derive: // lat = arcsin (z) // lon = arctan (y/x) - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); - out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, - (std::asin (diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt] - center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F) / AI_MATH_TWO_PI_F, + (std::asin(diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); } - } - else if (axis * base_axis_y >= angle_epsilon) { + } else if (axis * base_axis_y >= angle_epsilon) { // ... just the same again - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); - out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, - (std::asin (diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt] - center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F) / AI_MATH_TWO_PI_F, + (std::asin(diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); } - } - else if (axis * base_axis_z >= angle_epsilon) { + } else if (axis * base_axis_z >= angle_epsilon) { // ... just the same again - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); - out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, - (std::asin (diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D diff = (mesh->mVertices[pnt] - center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F) / AI_MATH_TWO_PI_F, + (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); } } // slower code path in case the mapping axis is not one of the coordinate system axes - else { + else { aiMatrix4x4 mTrafo; - aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); + aiMatrix4x4::FromToMatrix(axis, base_axis_y, mTrafo); // again the same, except we're applying a transformation now - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D diff = ((mTrafo*mesh->mVertices[pnt])-center).Normalize(); - out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F, - (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D diff = ((mTrafo * mesh->mVertices[pnt]) - center).Normalize(); + out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F) / AI_MATH_TWO_PI_F, + (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0); } } - // Now find and remove UV seams. A seam occurs if a face has a tcoord // close to zero on the one side, and a tcoord close to one on the // other side. - RemoveUVSeams(mesh,out); + RemoveUVSeams(mesh, out); } // ------------------------------------------------------------------------------------------------ -void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) -{ +void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh *mesh, const aiVector3D &axis, aiVector3D *out) { aiVector3D center, min, max; // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... // currently the mapping axis will always be one of x,y,z, except if the // PretransformVertices step is used (it transforms the meshes into worldspace, // thus changing the mapping axis) - if (axis * base_axis_x >= angle_epsilon) { + if (axis * base_axis_x >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); const ai_real diff = max.x - min.x; @@ -259,116 +226,110 @@ void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector // directly to the texture V axis. The other axis is derived from // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where // 'c' is the center point of the mesh. - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - aiVector3D& uv = out[pnt]; + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + aiVector3D &uv = out[pnt]; uv.y = (pos.x - min.x) / diff; - uv.x = (std::atan2( pos.z - center.z, pos.y - center.y) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + uv.x = (std::atan2(pos.z - center.z, pos.y - center.y) + (ai_real)AI_MATH_PI) / (ai_real)AI_MATH_TWO_PI; } - } - else if (axis * base_axis_y >= angle_epsilon) { + } else if (axis * base_axis_y >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); const ai_real diff = max.y - min.y; // just the same ... - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - aiVector3D& uv = out[pnt]; + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + aiVector3D &uv = out[pnt]; uv.y = (pos.y - min.y) / diff; - uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + uv.x = (std::atan2(pos.x - center.x, pos.z - center.z) + (ai_real)AI_MATH_PI) / (ai_real)AI_MATH_TWO_PI; } - } - else if (axis * base_axis_z >= angle_epsilon) { + } else if (axis * base_axis_z >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); const ai_real diff = max.z - min.z; // just the same ... - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - aiVector3D& uv = out[pnt]; + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + aiVector3D &uv = out[pnt]; uv.y = (pos.z - min.z) / diff; - uv.x = (std::atan2( pos.y - center.y, pos.x - center.x) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + uv.x = (std::atan2(pos.y - center.y, pos.x - center.x) + (ai_real)AI_MATH_PI) / (ai_real)AI_MATH_TWO_PI; } } // slower code path in case the mapping axis is not one of the coordinate system axes else { aiMatrix4x4 mTrafo; - aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); - FindMeshCenterTransformed(mesh, center, min, max,mTrafo); + aiMatrix4x4::FromToMatrix(axis, base_axis_y, mTrafo); + FindMeshCenterTransformed(mesh, center, min, max, mTrafo); const ai_real diff = max.y - min.y; // again the same, except we're applying a transformation now - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt){ - const aiVector3D pos = mTrafo* mesh->mVertices[pnt]; - aiVector3D& uv = out[pnt]; + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D pos = mTrafo * mesh->mVertices[pnt]; + aiVector3D &uv = out[pnt]; uv.y = (pos.y - min.y) / diff; - uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI; + uv.x = (std::atan2(pos.x - center.x, pos.z - center.z) + (ai_real)AI_MATH_PI) / (ai_real)AI_MATH_TWO_PI; } } // Now find and remove UV seams. A seam occurs if a face has a tcoord // close to zero on the one side, and a tcoord close to one on the // other side. - RemoveUVSeams(mesh,out); + RemoveUVSeams(mesh, out); } // ------------------------------------------------------------------------------------------------ -void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out) -{ - ai_real diffu,diffv; +void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh *mesh, const aiVector3D &axis, aiVector3D *out) { + ai_real diffu, diffv; aiVector3D center, min, max; // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ... // currently the mapping axis will always be one of x,y,z, except if the // PretransformVertices step is used (it transforms the meshes into worldspace, // thus changing the mapping axis) - if (axis * base_axis_x >= angle_epsilon) { + if (axis * base_axis_x >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); diffu = max.z - min.z; diffv = max.y - min.y; - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - out[pnt].Set((pos.z - min.z) / diffu,(pos.y - min.y) / diffv,0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.z - min.z) / diffu, (pos.y - min.y) / diffv, 0.0); } - } - else if (axis * base_axis_y >= angle_epsilon) { + } else if (axis * base_axis_y >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); diffu = max.x - min.x; diffv = max.z - min.z; - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.x - min.x) / diffu, (pos.z - min.z) / diffv, 0.0); } - } - else if (axis * base_axis_z >= angle_epsilon) { + } else if (axis * base_axis_z >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); diffu = max.x - min.x; diffv = max.y - min.y; - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { - const aiVector3D& pos = mesh->mVertices[pnt]; - out[pnt].Set((pos.x - min.x) / diffu,(pos.y - min.y) / diffv,0.0); + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { + const aiVector3D &pos = mesh->mVertices[pnt]; + out[pnt].Set((pos.x - min.x) / diffu, (pos.y - min.y) / diffv, 0.0); } } // slower code path in case the mapping axis is not one of the coordinate system axes - else - { + else { aiMatrix4x4 mTrafo; - aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo); - FindMeshCenterTransformed(mesh, center, min, max,mTrafo); + aiMatrix4x4::FromToMatrix(axis, base_axis_y, mTrafo); + FindMeshCenterTransformed(mesh, center, min, max, mTrafo); diffu = max.x - min.x; diffv = max.z - min.z; // again the same, except we're applying a transformation now - for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { + for (unsigned int pnt = 0; pnt < mesh->mNumVertices; ++pnt) { const aiVector3D pos = mTrafo * mesh->mVertices[pnt]; - out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0); + out[pnt].Set((pos.x - min.x) / diffu, (pos.z - min.z) / diffv, 0.0); } } @@ -376,41 +337,38 @@ void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& } // ------------------------------------------------------------------------------------------------ -void ComputeUVMappingProcess::ComputeBoxMapping( aiMesh*, aiVector3D* ) -{ +void ComputeUVMappingProcess::ComputeBoxMapping(aiMesh *, aiVector3D *) { ASSIMP_LOG_ERROR("Mapping type currently not implemented"); } // ------------------------------------------------------------------------------------------------ -void ComputeUVMappingProcess::Execute( aiScene* pScene) -{ +void ComputeUVMappingProcess::Execute(aiScene *pScene) { ASSIMP_LOG_DEBUG("GenUVCoordsProcess begin"); char buffer[1024]; - if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + } std::list mappingStack; - /* Iterate through all materials and search for non-UV mapped textures - */ - for (unsigned int i = 0; i < pScene->mNumMaterials;++i) - { + // Iterate through all materials and search for non-UV mapped textures + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { mappingStack.clear(); - aiMaterial* mat = pScene->mMaterials[i]; - for (unsigned int a = 0; a < mat->mNumProperties;++a) - { - aiMaterialProperty* prop = mat->mProperties[a]; - if (!::strcmp( prop->mKey.data, "$tex.mapping")) - { - aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData); - if (aiTextureMapping_UV != mapping) - { - if (!DefaultLogger::isNullLogger()) - { + aiMaterial *mat = pScene->mMaterials[i]; + if (mat == nullptr) { + ASSIMP_LOG_INFO("Material pointer in nullptr, skipping."); + continue; + } + for (unsigned int a = 0; a < mat->mNumProperties; ++a) { + aiMaterialProperty *prop = mat->mProperties[a]; + if (!::strcmp(prop->mKey.data, "$tex.mapping")) { + aiTextureMapping &mapping = *((aiTextureMapping *)prop->mData); + if (aiTextureMapping_UV != mapping) { + if (!DefaultLogger::isNullLogger()) { ai_snprintf(buffer, 1024, "Found non-UV mapped texture (%s,%u). Mapping type: %s", - aiTextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex, - MappingTypeToString(mapping)); + aiTextureTypeToString((aiTextureType)prop->mSemantic), prop->mIndex, + MappingTypeToString(mapping)); ASSIMP_LOG_INFO(buffer); } @@ -418,70 +376,62 @@ void ComputeUVMappingProcess::Execute( aiScene* pScene) if (aiTextureMapping_OTHER == mapping) continue; - MappingInfo info (mapping); + MappingInfo info(mapping); // Get further properties - currently only the major axis - for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) - { - aiMaterialProperty* prop2 = mat->mProperties[a2]; + for (unsigned int a2 = 0; a2 < mat->mNumProperties; ++a2) { + aiMaterialProperty *prop2 = mat->mProperties[a2]; if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex) continue; - if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis")) { - info.axis = *((aiVector3D*)prop2->mData); + if (!::strcmp(prop2->mKey.data, "$tex.mapaxis")) { + info.axis = *((aiVector3D *)prop2->mData); break; } } - unsigned int idx( 99999999 ); + unsigned int idx(99999999); // Check whether we have this mapping mode already - std::list::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info); - if (mappingStack.end() != it) - { + std::list::iterator it = std::find(mappingStack.begin(), mappingStack.end(), info); + if (mappingStack.end() != it) { idx = (*it).uv; - } - else - { + } else { /* We have found a non-UV mapped texture. Now - * we need to find all meshes using this material - * that we can compute UV channels for them. - */ - for (unsigned int m = 0; m < pScene->mNumMeshes;++m) - { - aiMesh* mesh = pScene->mMeshes[m]; + * we need to find all meshes using this material + * that we can compute UV channels for them. + */ + for (unsigned int m = 0; m < pScene->mNumMeshes; ++m) { + aiMesh *mesh = pScene->mMeshes[m]; unsigned int outIdx = 0; - if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == UINT_MAX || - !mesh->mNumVertices) - { + if (mesh->mMaterialIndex != i || (outIdx = FindEmptyUVChannel(mesh)) == UINT_MAX || + !mesh->mNumVertices) { continue; } // Allocate output storage - aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices]; + aiVector3D *p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices]; - switch (mapping) - { + switch (mapping) { case aiTextureMapping_SPHERE: - ComputeSphereMapping(mesh,info.axis,p); + ComputeSphereMapping(mesh, info.axis, p); break; case aiTextureMapping_CYLINDER: - ComputeCylinderMapping(mesh,info.axis,p); + ComputeCylinderMapping(mesh, info.axis, p); break; case aiTextureMapping_PLANE: - ComputePlaneMapping(mesh,info.axis,p); + ComputePlaneMapping(mesh, info.axis, p); break; case aiTextureMapping_BOX: - ComputeBoxMapping(mesh,p); + ComputeBoxMapping(mesh, p); break; default: ai_assert(false); } - if (m && idx != outIdx) - { + if (m && idx != outIdx) { ASSIMP_LOG_WARN("UV index mismatch. Not all meshes assigned to " - "this material have equal numbers of UV channels. The UV index stored in " - "the material structure does therefore not apply for all meshes. "); + "this material have equal numbers of UV channels. The UV index stored in " + "the material structure does therefore not apply for all meshes. "); } idx = outIdx; } @@ -491,7 +441,7 @@ void ComputeUVMappingProcess::Execute( aiScene* pScene) // Update the material property list mapping = aiTextureMapping_UV; - ((aiMaterial*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex)); + ((aiMaterial *)mat)->AddProperty(&idx, 1, AI_MATKEY_UVWSRC(prop->mSemantic, prop->mIndex)); } } } diff --git a/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.h b/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.h index 74744be7f..2a40a15b1 100644 --- a/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/ComputeUVMappingProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,13 +59,10 @@ namespace Assimp { /** ComputeUVMappingProcess - converts special mappings, such as spherical, * cylindrical or boxed to proper UV coordinates for rendering. */ -class ComputeUVMappingProcess : public BaseProcess -{ -public: - ComputeUVMappingProcess(); - ~ComputeUVMappingProcess(); - +class ComputeUVMappingProcess : public BaseProcess { public: + ComputeUVMappingProcess() = default; + ~ComputeUVMappingProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -73,14 +70,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: @@ -125,8 +122,7 @@ protected: private: // temporary structure to describe a mapping - struct MappingInfo - { + struct MappingInfo { explicit MappingInfo(aiTextureMapping _type) : type (_type) , axis (0.f,1.f,0.f) @@ -137,8 +133,7 @@ private: aiVector3D axis; unsigned int uv; - bool operator== (const MappingInfo& other) - { + bool operator== (const MappingInfo& other) { return type == other.type && axis == other.axis; } }; diff --git a/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.cpp b/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.cpp index 359c5a284..77c7cb853 100644 --- a/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -79,14 +77,6 @@ void flipUVs(aiMeshType *pMesh) { } // namespace -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -MakeLeftHandedProcess::MakeLeftHandedProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MakeLeftHandedProcess::~MakeLeftHandedProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool MakeLeftHandedProcess::IsActive(unsigned int pFlags) const { @@ -121,6 +111,12 @@ void MakeLeftHandedProcess::Execute(aiScene *pScene) { ProcessAnimation(nodeAnim); } } + + // process the cameras accordingly + for( unsigned int a = 0; a < pScene->mNumCameras; ++a) + { + ProcessCamera(pScene->mCameras[a]); + } ASSIMP_LOG_DEBUG("MakeLeftHandedProcess finished"); } @@ -227,18 +223,18 @@ void MakeLeftHandedProcess::ProcessAnimation(aiNodeAnim *pAnim) { // rotation keys for (unsigned int a = 0; a < pAnim->mNumRotationKeys; a++) { - /* That's the safe version, but the float errors add up. So we try the short version instead - aiMatrix3x3 rotmat = pAnim->mRotationKeys[a].mValue.GetMatrix(); - rotmat.a3 = -rotmat.a3; rotmat.b3 = -rotmat.b3; - rotmat.c1 = -rotmat.c1; rotmat.c2 = -rotmat.c2; - aiQuaternion rotquat( rotmat); - pAnim->mRotationKeys[a].mValue = rotquat; - */ pAnim->mRotationKeys[a].mValue.x *= -1.0f; pAnim->mRotationKeys[a].mValue.y *= -1.0f; } } +// ------------------------------------------------------------------------------------------------ +// Converts a single camera to left handed coordinates. +void MakeLeftHandedProcess::ProcessCamera( aiCamera* pCam) +{ + pCam->mLookAt = ai_real(2.0f) * pCam->mPosition - pCam->mLookAt; +} + #endif // !! ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS #ifndef ASSIMP_BUILD_NO_FLIPUVS_PROCESS // # FlipUVsProcess @@ -305,14 +301,6 @@ void FlipUVsProcess::ProcessMesh(aiMesh *pMesh) { #ifndef ASSIMP_BUILD_NO_FLIPWINDING_PROCESS // # FlipWindingOrderProcess -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -FlipWindingOrderProcess::FlipWindingOrderProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FlipWindingOrderProcess::~FlipWindingOrderProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FlipWindingOrderProcess::IsActive(unsigned int pFlags) const { diff --git a/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.h b/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.h index 474056c3a..e5ef19a8d 100644 --- a/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/ConvertToLHProcess.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,6 +58,7 @@ struct aiMesh; struct aiNodeAnim; struct aiNode; struct aiMaterial; +struct aiCamera; namespace Assimp { @@ -72,22 +72,18 @@ namespace Assimp { * * @note RH-LH and LH-RH is the same, so this class can be used for both */ -class MakeLeftHandedProcess : public BaseProcess -{ - - +class MakeLeftHandedProcess : public BaseProcess { public: - MakeLeftHandedProcess(); - ~MakeLeftHandedProcess(); + MakeLeftHandedProcess() = default; + ~MakeLeftHandedProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: - // ------------------------------------------------------------------- /** Recursively converts a node and all of its children */ @@ -114,30 +110,36 @@ protected: * @param pAnim The bone animation to transform */ void ProcessAnimation( aiNodeAnim* pAnim); + + // ------------------------------------------------------------------- + /** Converts a single camera to left handed coordinates. + * The camera viewing direction is inverted by reflecting mLookAt + * across mPosition. + * @param pCam The camera to convert + */ + void ProcessCamera( aiCamera* pCam); }; // --------------------------------------------------------------------------- /** Postprocessing step to flip the face order of the imported data */ -class FlipWindingOrderProcess : public BaseProcess -{ +class FlipWindingOrderProcess : public BaseProcess { friend class Importer; public: /** Constructor to be privately used by Importer */ - FlipWindingOrderProcess(); + FlipWindingOrderProcess() = default; /** Destructor, private as well */ - ~FlipWindingOrderProcess(); + ~FlipWindingOrderProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; -public: /** Some other types of post-processing require winding order flips */ static void ProcessMesh( aiMesh* pMesh); }; diff --git a/Engine/lib/assimp/code/PostProcessing/DeboneProcess.cpp b/Engine/lib/assimp/code/PostProcessing/DeboneProcess.cpp index bbf3264a5..1ae79ee30 100644 --- a/Engine/lib/assimp/code/PostProcessing/DeboneProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/DeboneProcess.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -43,42 +42,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// @file DeboneProcess.cpp /** Implementation of the DeboneProcess post processing step */ - - // internal headers of the post-processing framework #include "ProcessHelper.h" #include "DeboneProcess.h" #include - using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -DeboneProcess::DeboneProcess() -{ - mNumBones = 0; - mNumBonesCanDoWithout = 0; - - mThreshold = AI_DEBONE_THRESHOLD; - mAllOrNone = false; -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -DeboneProcess::~DeboneProcess() = default; +DeboneProcess::DeboneProcess() : mNumBones(0), mNumBonesCanDoWithout(0), mThreshold(AI_DEBONE_THRESHOLD), mAllOrNone(false) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool DeboneProcess::IsActive( unsigned int pFlags) const -{ +bool DeboneProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_Debone) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void DeboneProcess::SetupProperties(const Importer* pImp) -{ +void DeboneProcess::SetupProperties(const Importer* pImp) { // get the current value of the property mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false; mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD); @@ -86,8 +69,7 @@ void DeboneProcess::SetupProperties(const Importer* pImp) // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void DeboneProcess::Execute( aiScene* pScene) -{ +void DeboneProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("DeboneProcess begin"); if(!pScene->mNumMeshes) { @@ -104,7 +86,7 @@ void DeboneProcess::Execute( aiScene* pScene) if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones)) { for(unsigned int a = 0; a < pScene->mNumMeshes; a++) { if(splitList[a]) { - numSplits++; + ++numSplits; } } } @@ -117,10 +99,8 @@ void DeboneProcess::Execute( aiScene* pScene) // build a new array of meshes for the scene std::vector meshes; - for(unsigned int a=0;amNumMeshes;a++) - { + for (unsigned int a=0;amNumMeshes; ++a) { aiMesh* srcMesh = pScene->mMeshes[a]; - std::vector > newMeshes; if(splitList[a]) { @@ -133,13 +113,13 @@ void DeboneProcess::Execute( aiScene* pScene) // store new meshes and indices of the new meshes for(unsigned int b=0;bmName:0; + const aiString *find = newMeshes[b].second ? &newMeshes[b].second->mName : nullptr; - aiNode *theNode = find?pScene->mRootNode->FindNode(*find):0; + aiNode *theNode = find ? pScene->mRootNode->FindNode(*find) : nullptr; std::pair push_pair(static_cast(meshes.size()),theNode); - mSubMeshIndices[a].push_back(push_pair); - meshes.push_back(newMeshes[b].first); + mSubMeshIndices[a].emplace_back(push_pair); + meshes.emplace_back(newMeshes[b].first); out+=newMeshes[b].first->mNumBones; } @@ -150,10 +130,9 @@ void DeboneProcess::Execute( aiScene* pScene) // and destroy the source mesh. It should be completely contained inside the new submeshes delete srcMesh; - } - else { + } else { // Mesh is kept unchanged - store it's new place in the mesh array - mSubMeshIndices[a].emplace_back(static_cast(meshes.size()), (aiNode *)0); + mSubMeshIndices[a].emplace_back(static_cast(meshes.size()), (aiNode *)nullptr); meshes.push_back(srcMesh); } } @@ -173,8 +152,7 @@ void DeboneProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Counts bones total/removable in a given mesh. -bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) -{ +bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) { if(!pMesh->HasBones()) { return false; } @@ -193,25 +171,23 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) for(unsigned int i=0;imNumBones;i++) { for(unsigned int j=0;jmBones[i]->mNumWeights;j++) { float w = pMesh->mBones[i]->mWeights[j].mWeight; - - if(w==0.0f) { + if (w == 0.0f) { continue; } unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; - if(w>=mThreshold) { - - if(vertexBones[vid]!=cUnowned) { - if(vertexBones[vid]==i) //double entry - { + if (w >= mThreshold) { + if (vertexBones[vid] != cUnowned) { + //double entry + if(vertexBones[vid]==i) { ASSIMP_LOG_WARN("Encountered double entry in bone weights"); - } - else //TODO: track attraction in order to break tie - { + } else { + //TODO: track attraction in order to break tie vertexBones[vid] = cCoowned; } - } - else vertexBones[vid] = i; + } else { + vertexBones[vid] = i; + } } if(!isBoneNecessary[i]) { @@ -227,13 +203,16 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) if(isInterstitialRequired) { for(unsigned int i=0;imNumFaces;i++) { unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; - - for(unsigned int j=1;jmFaces[i].mNumIndices;j++) { + for (unsigned int j=1;jmFaces[i].mNumIndices;j++) { unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; - if(v!=w) { - if(vmNumBones) isBoneNecessary[v] = true; - if(wmNumBones) isBoneNecessary[w] = true; + if (v != w) { + if(vmNumBones) { + isBoneNecessary[v] = true; + } + if (wmNumBones) { + isBoneNecessary[w] = true; + } } } } @@ -252,8 +231,7 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) // ------------------------------------------------------------------------------------------------ // Splits the given mesh by bone count. -void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const -{ +void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const { // same deal here as ConsiderMesh basically std::vector isBoneNecessary(pMesh->mNumBones,false); @@ -341,7 +319,7 @@ void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMe } aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0); - std::pair push_pair(baseMesh,(const aiBone*)0); + std::pair push_pair(baseMesh, (const aiBone *)nullptr); poNewMeshes.push_back(push_pair); } @@ -371,8 +349,7 @@ void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMe // ------------------------------------------------------------------------------------------------ // Recursively updates the node's mesh list to account for the changed mesh list -void DeboneProcess::UpdateNode(aiNode* pNode) const -{ +void DeboneProcess::UpdateNode(aiNode* pNode) const { // rebuild the node's mesh index list std::vector newMeshList; @@ -382,9 +359,7 @@ void DeboneProcess::UpdateNode(aiNode* pNode) const unsigned int m = static_cast(pNode->mNumMeshes), n = static_cast(mSubMeshIndices.size()); // first pass, look for meshes which have not moved - for(unsigned int a=0;amMeshes[a]; const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex]; unsigned int nSubmeshes = static_cast(subMeshes.size()); @@ -398,8 +373,7 @@ void DeboneProcess::UpdateNode(aiNode* pNode) const // second pass, collect deboned meshes - for(unsigned int a=0;a > &subMeshes = mSubMeshIndices[a]; unsigned int nSubmeshes = static_cast(subMeshes.size()); @@ -430,8 +404,7 @@ void DeboneProcess::UpdateNode(aiNode* pNode) const // ------------------------------------------------------------------------------------------------ // Apply the node transformation to a mesh -void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const -{ +void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const { // Check whether we need to transform the coordinates at all if (!mat.IsIdentity()) { diff --git a/Engine/lib/assimp/code/PostProcessing/DeboneProcess.h b/Engine/lib/assimp/code/PostProcessing/DeboneProcess.h index cb072b7eb..cc2d43cb7 100644 --- a/Engine/lib/assimp/code/PostProcessing/DeboneProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/DeboneProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -70,7 +70,7 @@ namespace Assimp { class DeboneProcess : public BaseProcess { public: DeboneProcess(); - ~DeboneProcess(); + ~DeboneProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. @@ -79,14 +79,14 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; protected: // ------------------------------------------------------------------- @@ -94,7 +94,7 @@ protected: * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Counts bones total/removable in a given mesh. diff --git a/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp b/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp index f85daa588..29967b74b 100644 --- a/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -42,35 +42,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Implementation of the post processing step to drop face -* normals for all imported faces. -*/ - + * normals for all imported faces. + */ #include "DropFaceNormalsProcess.h" +#include #include #include #include -#include using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -DropFaceNormalsProcess::DropFaceNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -DropFaceNormalsProcess::~DropFaceNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool DropFaceNormalsProcess::IsActive( unsigned int pFlags) const { - return (pFlags & aiProcess_DropNormals) != 0; +bool DropFaceNormalsProcess::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_DropNormals) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void DropFaceNormalsProcess::Execute( aiScene* pScene) { +void DropFaceNormalsProcess::Execute(aiScene *pScene) { ASSIMP_LOG_DEBUG("DropFaceNormalsProcess begin"); if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { @@ -78,21 +69,21 @@ void DropFaceNormalsProcess::Execute( aiScene* pScene) { } bool bHas = false; - for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { - bHas |= this->DropMeshFaceNormals( pScene->mMeshes[a]); + for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { + bHas |= this->DropMeshFaceNormals(pScene->mMeshes[a]); } - if (bHas) { + if (bHas) { ASSIMP_LOG_INFO("DropFaceNormalsProcess finished. " - "Face normals have been removed"); + "Face normals have been removed"); } else { ASSIMP_LOG_DEBUG("DropFaceNormalsProcess finished. " - "No normals were present"); + "No normals were present"); } } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -bool DropFaceNormalsProcess::DropMeshFaceNormals (aiMesh* mesh) { +bool DropFaceNormalsProcess::DropMeshFaceNormals(aiMesh *mesh) { ai_assert(nullptr != mesh); if (nullptr == mesh->mNormals) { diff --git a/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.h b/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.h index 50abdc727..84f9dbe83 100644 --- a/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/DropFaceNormalsProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -55,8 +55,8 @@ namespace Assimp { */ class ASSIMP_API_WINONLY DropFaceNormalsProcess : public BaseProcess { public: - DropFaceNormalsProcess(); - ~DropFaceNormalsProcess(); + DropFaceNormalsProcess() = default; + ~DropFaceNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -64,15 +64,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; private: bool DropMeshFaceNormals(aiMesh* pcMesh); diff --git a/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.cpp b/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.cpp index dc7e54ac1..56b8c645e 100644 --- a/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -46,13 +46,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ProcessHelper.h" #include +#include using namespace Assimp; -EmbedTexturesProcess::EmbedTexturesProcess() = default; - -EmbedTexturesProcess::~EmbedTexturesProcess() = default; - bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { return (pFlags & aiProcess_EmbedTextures) != 0; } @@ -95,25 +92,47 @@ void EmbedTexturesProcess::Execute(aiScene* pScene) { ASSIMP_LOG_INFO("EmbedTexturesProcess finished. Embedded ", embeddedTexturesCount, " textures." ); } +std::string EmbedTexturesProcess::tryToFindValidPath(const std::string &imagePath) const +{ + // Test path directly + if (mIOHandler->Exists(imagePath)) { + return imagePath; + } + ASSIMP_LOG_WARN("EmbedTexturesProcess: Cannot find image: ", imagePath, ". Will try to find it in root folder."); + + // Test path in root path + std::string testPath = mRootPath + imagePath; + if (mIOHandler->Exists(testPath)) { + return testPath; + } + + // Test path basename in root path + testPath = mRootPath + imagePath.substr(imagePath.find_last_of("\\/") + 1u); + if (mIOHandler->Exists(testPath)) { + return testPath; + } + + // In unix systems, '\' is a valid file name character, but some files may use \ as a directory separator. + // Try replacing '\' by '/'. + if (mIOHandler->getOsSeparator() != '\\' && imagePath.find('\\') != std::string::npos) { + ASSIMP_LOG_WARN("EmbedTexturesProcess: Cannot find image '", imagePath, "' in root folder. Will try replacing directory separators."); + testPath = imagePath; + std::replace(testPath.begin(), testPath.end(), '\\', mIOHandler->getOsSeparator()); + return tryToFindValidPath(testPath); + } + + ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", imagePath, "."); + return {}; +} + bool EmbedTexturesProcess::addTexture(aiScene *pScene, const std::string &path) const { std::streampos imageSize = 0; - std::string imagePath = path; + std::string imagePath = tryToFindValidPath(path); - // Test path directly - if (!mIOHandler->Exists(imagePath)) { - ASSIMP_LOG_WARN("EmbedTexturesProcess: Cannot find image: ", imagePath, ". Will try to find it in root folder."); - - // Test path in root path - imagePath = mRootPath + path; - if (!mIOHandler->Exists(imagePath)) { - // Test path basename in root path - imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u); - if (!mIOHandler->Exists(imagePath)) { - ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); - return false; - } - } + if (imagePath.empty()) { + return false; } + IOStream* pFile = mIOHandler->Open(imagePath); if (pFile == nullptr) { ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); diff --git a/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.h b/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.h index c3e63612c..9ec5e1b18 100644 --- a/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/EmbedTexturesProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,21 +62,23 @@ namespace Assimp { class ASSIMP_API EmbedTexturesProcess : public BaseProcess { public: /// The default class constructor. - EmbedTexturesProcess(); + EmbedTexturesProcess() = default; /// The class destructor. - virtual ~EmbedTexturesProcess(); + ~EmbedTexturesProcess() override = default; /// Overwritten, @see BaseProcess - virtual bool IsActive(unsigned int pFlags) const; + bool IsActive(unsigned int pFlags) const override; /// Overwritten, @see BaseProcess - virtual void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; /// Overwritten, @see BaseProcess - virtual void Execute(aiScene* pScene); + virtual void Execute(aiScene* pScene) override; private: + // Try several ways to attempt to resolve the image path + std::string tryToFindValidPath(const std::string &imagePath) const; // Resolve the path and add the file content to the scene as a texture. bool addTexture(aiScene *pScene, const std::string &path) const; diff --git a/Engine/lib/assimp/code/PostProcessing/FindDegenerates.cpp b/Engine/lib/assimp/code/PostProcessing/FindDegenerates.cpp index 344979949..7401ea0e7 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindDegenerates.cpp +++ b/Engine/lib/assimp/code/PostProcessing/FindDegenerates.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,10 +41,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file FindDegenerates.cpp * @brief Implementation of the FindDegenerates post-process step. -*/ + */ -#include "ProcessHelper.h" #include "FindDegenerates.h" +#include "Geometry/GeometryUtils.h" +#include "ProcessHelper.h" #include @@ -53,39 +54,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; // Correct node indices to meshes and remove references to deleted mesh -static void updateSceneGraph(aiNode* pNode, const std::unordered_map& meshMap); +static void updateSceneGraph(aiNode *pNode, const std::unordered_map &meshMap); // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer FindDegeneratesProcess::FindDegeneratesProcess() : - mConfigRemoveDegenerates( false ), - mConfigCheckAreaOfTriangle( false ){ + mConfigRemoveDegenerates(false), + mConfigCheckAreaOfTriangle(false) { // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindDegeneratesProcess::~FindDegeneratesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const { +bool FindDegeneratesProcess::IsActive(unsigned int pFlags) const { return 0 != (pFlags & aiProcess_FindDegenerates); } // ------------------------------------------------------------------------------------------------ // Setup import configuration -void FindDegeneratesProcess::SetupProperties(const Importer* pImp) { +void FindDegeneratesProcess::SetupProperties(const Importer *pImp) { // Get the current value of AI_CONFIG_PP_FD_REMOVE - mConfigRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE,0)); - mConfigCheckAreaOfTriangle = ( 0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_CHECKAREA) ); + mConfigRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE, 0)); + mConfigCheckAreaOfTriangle = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_CHECKAREA)); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void FindDegeneratesProcess::Execute( aiScene* pScene) { +void FindDegeneratesProcess::Execute(aiScene *pScene) { ASSIMP_LOG_DEBUG("FindDegeneratesProcess begin"); - if ( nullptr == pScene) { + if (nullptr == pScene) { return; } @@ -115,7 +112,7 @@ void FindDegeneratesProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("FindDegeneratesProcess finished"); } -static void updateSceneGraph(aiNode* pNode, const std::unordered_map& meshMap) { +static void updateSceneGraph(aiNode *pNode, const std::unordered_map &meshMap) { unsigned int targetIndex = 0; for (unsigned i = 0; i < pNode->mNumMeshes; ++i) { const unsigned int sourceMeshIndex = pNode->mMeshes[i]; @@ -126,102 +123,75 @@ static void updateSceneGraph(aiNode* pNode, const std::unordered_mapmNumMeshes = targetIndex; - //recurse to all children + // recurse to all children for (unsigned i = 0; i < pNode->mNumChildren; ++i) { updateSceneGraph(pNode->mChildren[i], meshMap); } } -static ai_real heron( ai_real a, ai_real b, ai_real c ) { - ai_real s = (a + b + c) / 2; - ai_real area = pow((s * ( s - a ) * ( s - b ) * ( s - c ) ), (ai_real)0.5 ); - return area; -} - -static ai_real distance3D( const aiVector3D &vA, aiVector3D &vB ) { - const ai_real lx = ( vB.x - vA.x ); - const ai_real ly = ( vB.y - vA.y ); - const ai_real lz = ( vB.z - vA.z ); - ai_real a = lx*lx + ly*ly + lz*lz; - ai_real d = pow( a, (ai_real)0.5 ); - - return d; -} - -static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) { - ai_real area = 0; - - aiVector3D vA( mesh->mVertices[ face.mIndices[ 0 ] ] ); - aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] ); - aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] ); - - ai_real a( distance3D( vA, vB ) ); - ai_real b( distance3D( vB, vC ) ); - ai_real c( distance3D( vC, vA ) ); - area = heron( a, b, c ); - - return area; -} - // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported mesh -bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { +bool FindDegeneratesProcess::ExecuteOnMesh(aiMesh *mesh) { mesh->mPrimitiveTypes = 0; std::vector remove_me; if (mConfigRemoveDegenerates) { - remove_me.resize( mesh->mNumFaces, false ); + remove_me.resize(mesh->mNumFaces, false); } unsigned int deg = 0, limit; - for ( unsigned int a = 0; a < mesh->mNumFaces; ++a ) { - aiFace& face = mesh->mFaces[a]; + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { + aiFace &face = mesh->mFaces[a]; bool first = true; + auto vertex_in_range = [numVertices = mesh->mNumVertices](unsigned int vertex_idx) { return vertex_idx < numVertices; }; // check whether the face contains degenerated entries for (unsigned int i = 0; i < face.mNumIndices; ++i) { + if (!std::all_of(face.mIndices, face.mIndices + face.mNumIndices, vertex_in_range)) + continue; + // Polygons with more than 4 points are allowed to have double points, that is // simulating polygons with holes just with concave polygons. However, // double points may not come directly after another. limit = face.mNumIndices; if (face.mNumIndices > 4) { - limit = std::min( limit, i+2 ); + limit = std::min(limit, i + 2); } - for (unsigned int t = i+1; t < limit; ++t) { - if (mesh->mVertices[face.mIndices[ i ] ] == mesh->mVertices[ face.mIndices[ t ] ]) { + for (unsigned int t = i + 1; t < limit; ++t) { + if (mesh->mVertices[face.mIndices[i]] == mesh->mVertices[face.mIndices[t]]) { // we have found a matching vertex position // remove the corresponding index from the array --face.mNumIndices; --limit; for (unsigned int m = t; m < face.mNumIndices; ++m) { - face.mIndices[ m ] = face.mIndices[ m+1 ]; + face.mIndices[m] = face.mIndices[m + 1]; } --t; // NOTE: we set the removed vertex index to an unique value // to make sure the developer gets notified when his // application attempts to access this data. - face.mIndices[ face.mNumIndices ] = 0xdeadbeef; + face.mIndices[face.mNumIndices] = 0xdeadbeef; - if(first) { + if (first) { ++deg; first = false; } - if ( mConfigRemoveDegenerates ) { - remove_me[ a ] = true; + if (mConfigRemoveDegenerates) { + remove_me[a] = true; goto evil_jump_outside; // hrhrhrh ... yeah, this rocks baby! } } } - if ( mConfigCheckAreaOfTriangle ) { - if ( face.mNumIndices == 3 ) { - ai_real area = calculateAreaOfTriangle( face, mesh ); + if (mConfigCheckAreaOfTriangle) { + if (face.mNumIndices == 3) { + ai_real area = GeometryUtils::calculateAreaOfTriangle(face, mesh); if (area < ai_epsilon) { - if ( mConfigRemoveDegenerates ) { - remove_me[ a ] = true; + if (mConfigRemoveDegenerates) { + remove_me[a] = true; ++deg; goto evil_jump_outside; } @@ -233,8 +203,7 @@ bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { } // We need to update the primitive flags array of the mesh. - switch (face.mNumIndices) - { + switch (face.mNumIndices) { case 1u: mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; break; @@ -248,30 +217,28 @@ bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; break; }; -evil_jump_outside: + evil_jump_outside: continue; } // If AI_CONFIG_PP_FD_REMOVE is true, remove degenerated faces from the import if (mConfigRemoveDegenerates && deg) { unsigned int n = 0; - for (unsigned int a = 0; a < mesh->mNumFaces; ++a) - { - aiFace& face_src = mesh->mFaces[a]; + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { + aiFace &face_src = mesh->mFaces[a]; if (!remove_me[a]) { - aiFace& face_dest = mesh->mFaces[n++]; + aiFace &face_dest = mesh->mFaces[n++]; // Do a manual copy, keep the index array face_dest.mNumIndices = face_src.mNumIndices; - face_dest.mIndices = face_src.mIndices; + face_dest.mIndices = face_src.mIndices; if (&face_src != &face_dest) { // clear source face_src.mNumIndices = 0; face_src.mIndices = nullptr; } - } - else { + } else { // Otherwise delete it if we don't need this face delete[] face_src.mIndices; face_src.mIndices = nullptr; @@ -281,15 +248,15 @@ evil_jump_outside: // Just leave the rest of the array unreferenced, we don't care for now mesh->mNumFaces = n; if (!mesh->mNumFaces) { - //The whole mesh consists of degenerated faces - //signal upward, that this mesh should be deleted. + // The whole mesh consists of degenerated faces + // signal upward, that this mesh should be deleted. ASSIMP_LOG_VERBOSE_DEBUG("FindDegeneratesProcess removed a mesh full of degenerated primitives"); return true; } } if (deg && !DefaultLogger::isNullLogger()) { - ASSIMP_LOG_WARN( "Found ", deg, " degenerated primitives"); + ASSIMP_LOG_WARN("Found ", deg, " degenerated primitives"); } return false; } diff --git a/Engine/lib/assimp/code/PostProcessing/FindDegenerates.h b/Engine/lib/assimp/code/PostProcessing/FindDegenerates.h index 6fe1e929b..0d046df2d 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindDegenerates.h +++ b/Engine/lib/assimp/code/PostProcessing/FindDegenerates.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,19 +59,19 @@ namespace Assimp { class ASSIMP_API FindDegeneratesProcess : public BaseProcess { public: FindDegeneratesProcess(); - ~FindDegeneratesProcess(); + ~FindDegeneratesProcess() override = default; // ------------------------------------------------------------------- // Check whether step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup import settings - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- // Execute step on a given mesh @@ -105,23 +105,19 @@ private: bool mConfigCheckAreaOfTriangle; }; -inline -void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) { +inline void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) { mConfigRemoveDegenerates = enabled; } -inline -bool FindDegeneratesProcess::IsInstantRemoval() const { +inline bool FindDegeneratesProcess::IsInstantRemoval() const { return mConfigRemoveDegenerates; } -inline -void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) { +inline void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) { mConfigCheckAreaOfTriangle = enabled; } -inline -bool FindDegeneratesProcess::isAreaCheckEnabled() const { +inline bool FindDegeneratesProcess::isAreaCheckEnabled() const { return mConfigCheckAreaOfTriangle; } diff --git a/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.cpp b/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.cpp index 07a0f66db..9186dd3dd 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -58,10 +58,6 @@ FindInstancesProcess::FindInstancesProcess() : configSpeedFlag (false) {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindInstancesProcess::~FindInstancesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FindInstancesProcess::IsActive( unsigned int pFlags) const diff --git a/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.h b/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.h index b501d88d5..63e988abf 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/FindInstancesProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -50,7 +50,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "PostProcessing/ProcessHelper.h" class FindInstancesProcessTest; -namespace Assimp { + +namespace Assimp { // ------------------------------------------------------------------------------- /** @brief Get a pseudo(!)-hash representing a mesh. @@ -60,8 +61,7 @@ namespace Assimp { * @param in Input mesh * @return Hash. */ -inline -uint64_t GetMeshHash(aiMesh* in) { +inline uint64_t GetMeshHash(aiMesh* in) { ai_assert(nullptr != in); // ... get an unique value representing the vertex format of the mesh @@ -83,8 +83,7 @@ uint64_t GetMeshHash(aiMesh* in) { * @param e Epsilon * @return true if the arrays are identical */ -inline -bool CompareArrays(const aiVector3D* first, const aiVector3D* second, +inline bool CompareArrays(const aiVector3D* first, const aiVector3D* second, unsigned int size, float e) { for (const aiVector3D* end = first+size; first != end; ++first,++second) { if ( (*first - *second).SquareLength() >= e) @@ -107,31 +106,27 @@ inline bool CompareArrays(const aiColor4D* first, const aiColor4D* second, // --------------------------------------------------------------------------- /** @brief A post-processing steps to search for instanced meshes */ -class FindInstancesProcess : public BaseProcess -{ +class FindInstancesProcess : public BaseProcess { public: - FindInstancesProcess(); - ~FindInstancesProcess(); + ~FindInstancesProcess() override = default; -public: // ------------------------------------------------------------------- // Check whether step is active in given flags combination - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup properties prior to executing the process - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; private: - bool configSpeedFlag; - }; // ! end class FindInstancesProcess + } // ! end namespace Assimp #endif // !! AI_FINDINSTANCES_H_INC diff --git a/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.cpp b/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.cpp index c65208cbd..12f345407 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,10 +60,6 @@ FindInvalidDataProcess::FindInvalidDataProcess() : // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindInvalidDataProcess::~FindInvalidDataProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FindInvalidDataProcess::IsActive(unsigned int pFlags) const { @@ -86,6 +82,9 @@ void UpdateMeshReferences(aiNode *node, const std::vector &meshMap for (unsigned int a = 0; a < node->mNumMeshes; ++a) { unsigned int ref = node->mMeshes[a]; + if (ref >= meshMapping.size()) + throw DeadlyImportError("Invalid mesh ref"); + if (UINT_MAX != (ref = meshMapping[ref])) { node->mMeshes[out++] = ref; } @@ -147,7 +146,13 @@ void FindInvalidDataProcess::Execute(aiScene *pScene) { // we need to remove some meshes. // therefore we'll also need to remove all references // to them from the scenegraph - UpdateMeshReferences(pScene->mRootNode, meshMapping); + try { + UpdateMeshReferences(pScene->mRootNode, meshMapping); + } catch (const std::exception&) { + // fix the real number of meshes otherwise we'll get double free in the scene destructor + pScene->mNumMeshes = real; + throw; + } pScene->mNumMeshes = real; } @@ -268,7 +273,8 @@ void FindInvalidDataProcess::ProcessAnimation(aiAnimation *anim) { void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) { ai_assert(nullptr != anim); if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) { - ai_assert_entry(); + ASSIMP_LOG_ERROR("Invalid node anuimation instance detected."); + return; } diff --git a/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.h b/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.h index 5ea895c59..516db4272 100644 --- a/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/FindInvalidDataProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -64,35 +64,37 @@ namespace Assimp { * which have zero normal vectors. */ class ASSIMP_API FindInvalidDataProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. FindInvalidDataProcess(); - ~FindInvalidDataProcess(); + ~FindInvalidDataProcess() override = default; // ------------------------------------------------------------------- - // - bool IsActive(unsigned int pFlags) const; + /// Returns active state. + bool IsActive(unsigned int pFlags) const override; // ------------------------------------------------------------------- - // Setup import settings - void SetupProperties(const Importer *pImp); + /// Setup import settings + void SetupProperties(const Importer *pImp) override; // ------------------------------------------------------------------- - // Run the step - void Execute(aiScene *pScene); + /// Run the step + void Execute(aiScene *pScene) override; // ------------------------------------------------------------------- - /** Executes the post-processing step on the given mesh - * @param pMesh The mesh to process. - * @return 0 - nothing, 1 - removed sth, 2 - please delete me */ + /// Executes the post-processing step on the given mesh + /// @param pMesh The mesh to process. + /// @return 0 - nothing, 1 - removed sth, 2 - please delete me */ int ProcessMesh(aiMesh *pMesh); // ------------------------------------------------------------------- - /** Executes the post-processing step on the given animation - * @param anim The animation to process. */ + /// Executes the post-processing step on the given animation + /// @param anim The animation to process. */ void ProcessAnimation(aiAnimation *anim); // ------------------------------------------------------------------- - /** Executes the post-processing step on the given anim channel - * @param anim The animation channel to process.*/ + /// Executes the post-processing step on the given anim channel + /// @param anim The animation channel to process.*/ void ProcessAnimationChannel(aiNodeAnim *anim); private: diff --git a/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.cpp b/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.cpp index 3791bd35a..2bf85430f 100644 --- a/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.cpp +++ b/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -56,26 +56,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -FixInfacingNormalsProcess::FixInfacingNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FixInfacingNormalsProcess::~FixInfacingNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const -{ +bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_FixInfacingNormals) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void FixInfacingNormalsProcess::Execute( aiScene* pScene) -{ +void FixInfacingNormalsProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess begin"); bool bHas( false ); diff --git a/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.h b/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.h index b7d3ba386..b25d92282 100644 --- a/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.h +++ b/Engine/lib/assimp/code/PostProcessing/FixNormalsStep.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -49,8 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The FixInfacingNormalsProcess tries to determine whether the normal @@ -59,8 +58,10 @@ namespace Assimp */ class FixInfacingNormalsProcess : public BaseProcess { public: - FixInfacingNormalsProcess(); - ~FixInfacingNormalsProcess(); + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + FixInfacingNormalsProcess() = default; + ~FixInfacingNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -68,14 +69,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: diff --git a/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp b/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp index 52a0861e5..da9fd7137 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -48,10 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -GenBoundingBoxesProcess::GenBoundingBoxesProcess() = default; - -GenBoundingBoxesProcess::~GenBoundingBoxesProcess() = default; - bool GenBoundingBoxesProcess::IsActive(unsigned int pFlags) const { return 0 != ( pFlags & aiProcess_GenBoundingBoxes ); } diff --git a/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.h b/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.h index 0b7591b6d..c24009dc9 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/GenBoundingBoxesProcess.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -19,7 +19,7 @@ conditions are met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - +s * Neither the name of the assimp team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior @@ -54,18 +54,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -/** Post-processing process to find axis-aligned bounding volumes for amm meshes - * used in a scene +/** + * @brief Post-processing process to find axis-aligned bounding volumes for amm meshes + * used in a scene. */ class ASSIMP_API GenBoundingBoxesProcess : public BaseProcess { public: - /// The class constructor. - GenBoundingBoxesProcess(); - /// The class destructor. - ~GenBoundingBoxesProcess(); - /// Will return true, if aiProcess_GenBoundingBoxes is defined. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + GenBoundingBoxesProcess() = default; + ~GenBoundingBoxesProcess() override = default; + + // ------------------------------------------------------------------- + /// @brief Will return true, if aiProcess_GenBoundingBoxes is defined. bool IsActive(unsigned int pFlags) const override; - /// The execution callback. + + // ------------------------------------------------------------------- + /// @brief The execution callback. void Execute(aiScene* pScene) override; }; diff --git a/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp b/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp index f104b98b6..426f23517 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,9 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/** @file Implementation of the post processing step to generate face -* normals for all imported faces. -*/ +/** + * @file Implementation of the post-processing step to generate face + * normals for all imported faces. + */ #include "GenFaceNormalsProcess.h" #include @@ -55,23 +54,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; // ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -GenFaceNormalsProcess::GenFaceNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -GenFaceNormalsProcess::~GenFaceNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Returns whether the processing step is present in the given flag field. +// Returns whether the processing step is in the given flag field. bool GenFaceNormalsProcess::IsActive(unsigned int pFlags) const { force_ = (pFlags & aiProcess_ForceGenNormals) != 0; flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0; + leftHanded_ = (pFlags & aiProcess_MakeLeftHanded) != 0; return (pFlags & aiProcess_GenNormals) != 0; } // ------------------------------------------------------------------------------------------------ -// Executes the post processing step on the given imported data. +// Executes the post-processing step on the given imported data. void GenFaceNormalsProcess::Execute(aiScene *pScene) { ASSIMP_LOG_DEBUG("GenFaceNormalsProcess begin"); @@ -95,7 +87,7 @@ void GenFaceNormalsProcess::Execute(aiScene *pScene) { } // ------------------------------------------------------------------------------------------------ -// Executes the post processing step on the given imported data. +// Executes the post-processing step on the given imported data. bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { if (nullptr != pMesh->mNormals) { if (force_) { @@ -114,8 +106,27 @@ bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { } // allocate an array to hold the output normals - pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; - const float qnan = get_qnan(); + std::vector normals; + normals.resize(pMesh->mNumVertices); + + // mask to indicate if a vertex was already referenced and needs to be duplicated + std::vector alreadyReferenced; + alreadyReferenced.resize(pMesh->mNumVertices, false); + std::vector duplicatedVertices; + + auto storeNormalSplitVertex = [&](unsigned int index, const aiVector3D& normal) { + if (!alreadyReferenced[index]) { + normals[index] = normal; + alreadyReferenced[index] = true; + } else { + duplicatedVertices.push_back(pMesh->mVertices[index]); + normals.push_back(normal); + index = pMesh->mNumVertices + static_cast(duplicatedVertices.size() - 1); + } + return index; + }; + + const aiVector3D undefinedNormal = aiVector3D(get_qnan()); // iterate through all faces and compute per-face normals but store them per-vertex. for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { @@ -123,7 +134,7 @@ bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { if (face.mNumIndices < 3) { // either a point or a line -> no well-defined normal vector for (unsigned int i = 0; i < face.mNumIndices; ++i) { - pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan); + face.mIndices[i] = storeNormalSplitVertex(face.mIndices[i], undefinedNormal); } continue; } @@ -131,13 +142,29 @@ bool GenFaceNormalsProcess::GenMeshFaceNormals(aiMesh *pMesh) { const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]]; const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]]; const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; - if (flippedWindingOrder_) - std::swap( pV2, pV3 ); + // Boolean XOR - if either but not both of these flags are set, then the winding order has + // changed and the cross-product to calculate the normal needs to be reversed + if (flippedWindingOrder_ != leftHanded_) + std::swap(pV2, pV3); const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); for (unsigned int i = 0; i < face.mNumIndices; ++i) { - pMesh->mNormals[face.mIndices[i]] = vNor; + face.mIndices[i] = storeNormalSplitVertex(face.mIndices[i], vNor); } } + + // store normals (and additional vertices) back into the mesh + if (!duplicatedVertices.empty()) { + const aiVector3D * oldVertices = pMesh->mVertices; + auto oldNumVertices = pMesh->mNumVertices; + pMesh->mNumVertices += static_cast(duplicatedVertices.size()); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + memcpy(pMesh->mVertices, oldVertices, oldNumVertices * sizeof(aiVector3D)); + memcpy(pMesh->mVertices + oldNumVertices, duplicatedVertices.data(), duplicatedVertices.size() * sizeof(aiVector3D)); + delete[] oldVertices; + } + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + memcpy(pMesh->mNormals, normals.data(), normals.size() * sizeof(aiVector3D)); + return true; } diff --git a/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.h b/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.h index 586c4902e..a5aad13d1 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/GenFaceNormalsProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -47,40 +47,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Common/BaseProcess.h" #include -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- -/** The GenFaceNormalsProcess computes face normals for all faces of all meshes -*/ -class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess -{ +/** + * @brief The GenFaceNormalsProcess computes face normals for all faces of all meshes + */ +class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + GenFaceNormalsProcess() = default; + ~GenFaceNormalsProcess() override = default; - GenFaceNormalsProcess(); - ~GenFaceNormalsProcess(); - -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. * @param pFlags The processing flags the importer was called with. A bitwise * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; private: bool GenMeshFaceNormals(aiMesh* pcMesh); mutable bool force_ = false; mutable bool flippedWindingOrder_ = false; + mutable bool leftHanded_ = false; }; } // end of namespace Assimp diff --git a/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp b/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp index 0cb2bddb1..f7fef6bc4 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,15 +58,12 @@ GenVertexNormalsProcess::GenVertexNormalsProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -GenVertexNormalsProcess::~GenVertexNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool GenVertexNormalsProcess::IsActive(unsigned int pFlags) const { force_ = (pFlags & aiProcess_ForceGenNormals) != 0; flippedWindingOrder_ = (pFlags & aiProcess_FlipWindingOrder) != 0; + leftHanded_ = (pFlags & aiProcess_MakeLeftHanded) != 0; return (pFlags & aiProcess_GenSmoothNormals) != 0; } @@ -108,10 +103,11 @@ void GenVertexNormalsProcess::Execute(aiScene *pScene) { // Executes the post processing step on the given imported data. bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int meshIndex) { if (nullptr != pMesh->mNormals) { - if (force_) - delete[] pMesh->mNormals; - else + if (!force_) { return false; + } + delete[] pMesh->mNormals; + pMesh->mNormals = nullptr; } // If the mesh consists of lines and/or points but not of @@ -141,8 +137,11 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int m const aiVector3D *pV1 = &pMesh->mVertices[face.mIndices[0]]; const aiVector3D *pV2 = &pMesh->mVertices[face.mIndices[1]]; const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; - if (flippedWindingOrder_) - std::swap( pV2, pV3 ); + // Boolean XOR - if either but not both of these flags is set, then the winding order has + // changed and the cross product to calculate the normal needs to be reversed + if (flippedWindingOrder_ != leftHanded_) { + std::swap(pV2, pV3); + } const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); for (unsigned int i = 0; i < face.mNumIndices; ++i) { diff --git a/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.h b/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.h index 0dcae793a..677c06a43 100644 --- a/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/GenVertexNormalsProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,8 +60,10 @@ namespace Assimp { */ class ASSIMP_API GenVertexNormalsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. GenVertexNormalsProcess(); - ~GenVertexNormalsProcess(); + ~GenVertexNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. @@ -70,22 +72,21 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; // setter for configMaxAngle inline void SetMaxSmoothAngle(ai_real f) { @@ -105,6 +106,7 @@ private: ai_real configMaxAngle; mutable bool force_ = false; mutable bool flippedWindingOrder_ = false; + mutable bool leftHanded_ = false; }; } // end of namespace Assimp diff --git a/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.cpp b/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.cpp index 197856171..e108354b1 100644 --- a/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,35 +57,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() -: mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) { +ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() : + mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) { // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ImproveCacheLocalityProcess::~ImproveCacheLocalityProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool ImproveCacheLocalityProcess::IsActive( unsigned int pFlags) const { +bool ImproveCacheLocalityProcess::IsActive(unsigned int pFlags) const { return (pFlags & aiProcess_ImproveCacheLocality) != 0; } // ------------------------------------------------------------------------------------------------ // Setup configuration -void ImproveCacheLocalityProcess::SetupProperties(const Importer* pImp) { +void ImproveCacheLocalityProcess::SetupProperties(const Importer *pImp) { // AI_CONFIG_PP_ICL_PTCACHE_SIZE controls the target cache size for the optimizer - mConfigCacheDepth = pImp->GetPropertyInteger(AI_CONFIG_PP_ICL_PTCACHE_SIZE,PP_ICL_PTCACHE_SIZE); + mConfigCacheDepth = pImp->GetPropertyInteger(AI_CONFIG_PP_ICL_PTCACHE_SIZE, PP_ICL_PTCACHE_SIZE); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void ImproveCacheLocalityProcess::Execute( aiScene* pScene) { +void ImproveCacheLocalityProcess::Execute(aiScene *pScene) { if (!pScene->mNumMeshes) { ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess skipped; there are no meshes"); return; @@ -97,11 +91,11 @@ void ImproveCacheLocalityProcess::Execute( aiScene* pScene) { float out = 0.f; unsigned int numf = 0, numm = 0; - for( unsigned int a = 0; a < pScene->mNumMeshes; ++a ){ - const float res = ProcessMesh( pScene->mMeshes[a],a); + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + const float res = ProcessMesh(pScene->mMeshes[a], a); if (res) { numf += pScene->mMeshes[a]->mNumFaces; - out += res; + out += res; ++numm; } } @@ -113,9 +107,54 @@ void ImproveCacheLocalityProcess::Execute( aiScene* pScene) { } } +// ------------------------------------------------------------------------------------------------ +static ai_real calculateInputACMR(aiMesh *pMesh, const aiFace *const pcEnd, + unsigned int configCacheDepth, unsigned int meshNum) { + ai_real fACMR = 0.0f; + unsigned int *piFIFOStack = new unsigned int[configCacheDepth]; + memset(piFIFOStack, 0xff, configCacheDepth * sizeof(unsigned int)); + unsigned int *piCur = piFIFOStack; + const unsigned int *const piCurEnd = piFIFOStack + configCacheDepth; + + // count the number of cache misses + unsigned int iCacheMisses = 0; + for (const aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) { + for (unsigned int qq = 0; qq < 3; ++qq) { + bool bInCache = false; + for (unsigned int *pp = piFIFOStack; pp < piCurEnd; ++pp) { + if (*pp == pcFace->mIndices[qq]) { + // the vertex is in cache + bInCache = true; + break; + } + } + if (!bInCache) { + ++iCacheMisses; + if (piCurEnd == piCur) { + piCur = piFIFOStack; + } + *piCur++ = pcFace->mIndices[qq]; + } + } + } + delete[] piFIFOStack; + fACMR = (ai_real)iCacheMisses / pMesh->mNumFaces; + if (3.0 == fACMR) { + char szBuff[128]; // should be sufficiently large in every case + + // the JoinIdenticalVertices process has not been executed on this + // mesh, otherwise this value would normally be at least minimally + // smaller than 3.0 ... + ai_snprintf(szBuff, 128, "Mesh %u: Not suitable for vcache optimization", meshNum); + ASSIMP_LOG_WARN(szBuff); + return static_cast(0.f); + } + return fACMR; +} + // ------------------------------------------------------------------------------------------------ // Improves the cache coherency of a specific mesh -ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum) { +ai_real ImproveCacheLocalityProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshNum) { // TODO: rewrite this to use std::vector or boost::shared_array ai_assert(nullptr != pMesh); @@ -130,91 +169,57 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me return static_cast(0.f); } - if(pMesh->mNumVertices <= mConfigCacheDepth) { + if (pMesh->mNumVertices <= mConfigCacheDepth) { return static_cast(0.f); } ai_real fACMR = 3.f; - const aiFace* const pcEnd = pMesh->mFaces+pMesh->mNumFaces; + const aiFace *const pcEnd = pMesh->mFaces + pMesh->mNumFaces; // Input ACMR is for logging purposes only - if (!DefaultLogger::isNullLogger()) { - - unsigned int* piFIFOStack = new unsigned int[mConfigCacheDepth]; - memset(piFIFOStack,0xff,mConfigCacheDepth*sizeof(unsigned int)); - unsigned int* piCur = piFIFOStack; - const unsigned int* const piCurEnd = piFIFOStack + mConfigCacheDepth; - - // count the number of cache misses - unsigned int iCacheMisses = 0; - for (const aiFace* pcFace = pMesh->mFaces;pcFace != pcEnd;++pcFace) { - for (unsigned int qq = 0; qq < 3;++qq) { - bool bInCache = false; - for (unsigned int* pp = piFIFOStack;pp < piCurEnd;++pp) { - if (*pp == pcFace->mIndices[qq]) { - // the vertex is in cache - bInCache = true; - break; - } - } - if (!bInCache) { - ++iCacheMisses; - if (piCurEnd == piCur) { - piCur = piFIFOStack; - } - *piCur++ = pcFace->mIndices[qq]; - } - } - } - delete[] piFIFOStack; - fACMR = (ai_real) iCacheMisses / pMesh->mNumFaces; - if (3.0 == fACMR) { - char szBuff[128]; // should be sufficiently large in every case - - // the JoinIdenticalVertices process has not been executed on this - // mesh, otherwise this value would normally be at least minimally - // smaller than 3.0 ... - ai_snprintf(szBuff,128,"Mesh %u: Not suitable for vcache optimization",meshNum); - ASSIMP_LOG_WARN(szBuff); - return static_cast(0.f); - } + if (!DefaultLogger::isNullLogger()) { + fACMR = calculateInputACMR(pMesh, pcEnd, mConfigCacheDepth, meshNum); } // first we need to build a vertex-triangle adjacency list - VertexTriangleAdjacency adj(pMesh->mFaces,pMesh->mNumFaces, pMesh->mNumVertices,true); + VertexTriangleAdjacency adj(pMesh->mFaces, pMesh->mNumFaces, pMesh->mNumVertices, true); // build a list to store per-vertex caching time stamps - unsigned int* const piCachingStamps = new unsigned int[pMesh->mNumVertices]; - memset(piCachingStamps,0x0,pMesh->mNumVertices*sizeof(unsigned int)); + std::vector piCachingStamps; + piCachingStamps.resize(pMesh->mNumVertices); + memset(&piCachingStamps[0], 0x0, pMesh->mNumVertices * sizeof(unsigned int)); // allocate an empty output index buffer. We store the output indices in one large array. // Since the number of triangles won't change the input faces can be reused. This is how // we save thousands of redundant mini allocations for aiFace::mIndices - const unsigned int iIdxCnt = pMesh->mNumFaces*3; - unsigned int* const piIBOutput = new unsigned int[iIdxCnt]; - unsigned int* piCSIter = piIBOutput; + const unsigned int iIdxCnt = pMesh->mNumFaces * 3; + std::vector piIBOutput; + piIBOutput.resize(iIdxCnt); + std::vector::iterator piCSIter = piIBOutput.begin(); // allocate the flag array to hold the information // whether a face has already been emitted or not - std::vector abEmitted(pMesh->mNumFaces,false); + std::vector abEmitted(pMesh->mNumFaces, false); // dead-end vertex index stack - std::stack > sDeadEndVStack; + std::stack> sDeadEndVStack; // create a copy of the piNumTriPtr buffer - unsigned int* const piNumTriPtr = adj.mLiveTriangles; + unsigned int *const piNumTriPtr = adj.mLiveTriangles; const std::vector piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + pMesh->mNumVertices); // get the largest number of referenced triangles and allocate the "candidate buffer" - unsigned int iMaxRefTris = 0; { - const unsigned int* piCur = adj.mLiveTriangles; - const unsigned int* const piCurEnd = adj.mLiveTriangles+pMesh->mNumVertices; - for (;piCur != piCurEnd;++piCur) { - iMaxRefTris = std::max(iMaxRefTris,*piCur); + unsigned int iMaxRefTris = 0; + { + const unsigned int *piCur = adj.mLiveTriangles; + const unsigned int *const piCurEnd = adj.mLiveTriangles + pMesh->mNumVertices; + for (; piCur != piCurEnd; ++piCur) { + iMaxRefTris = std::max(iMaxRefTris, *piCur); } } ai_assert(iMaxRefTris > 0); - unsigned int* piCandidates = new unsigned int[iMaxRefTris*3]; + std::vector piCandidates; + piCandidates.resize(iMaxRefTris * 3); unsigned int iCacheMisses = 0; // ................................................................................... @@ -249,23 +254,23 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me int ivdx = 0; int ics = 1; - int iStampCnt = mConfigCacheDepth+1; - while (ivdx >= 0) { + int iStampCnt = mConfigCacheDepth + 1; + while (ivdx >= 0) { unsigned int icnt = piNumTriPtrNoModify[ivdx]; - unsigned int* piList = adj.GetAdjacentTriangles(ivdx); - unsigned int* piCurCandidate = piCandidates; + unsigned int *piList = adj.GetAdjacentTriangles(ivdx); + std::vector::iterator piCurCandidate = piCandidates.begin(); // get all triangles in the neighborhood - for (unsigned int tri = 0; tri < icnt;++tri) { + for (unsigned int tri = 0; tri < icnt; ++tri) { // if they have not yet been emitted, add them to the output IB const unsigned int fidx = *piList++; - if (!abEmitted[fidx]) { + if (!abEmitted[fidx]) { // so iterate through all vertices of the current triangle - const aiFace* pcFace = &pMesh->mFaces[ fidx ]; - unsigned nind = pcFace->mNumIndices; + const aiFace *pcFace = &pMesh->mFaces[fidx]; + const unsigned nind = pcFace->mNumIndices; for (unsigned ind = 0; ind < nind; ind++) { unsigned dp = pcFace->mIndices[ind]; @@ -285,7 +290,7 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me *piCSIter++ = dp; // if the vertex is not yet in cache, set its cache count - if (iStampCnt-piCachingStamps[dp] > mConfigCacheDepth) { + if (iStampCnt - piCachingStamps[dp] > mConfigCacheDepth) { piCachingStamps[dp] = iStampCnt++; ++iCacheMisses; } @@ -301,16 +306,16 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me // get next fanning vertex ivdx = -1; int max_priority = -1; - for (unsigned int* piCur = piCandidates;piCur != piCurCandidate;++piCur) { + for (std::vector::iterator piCur = piCandidates.begin(); piCur != piCurCandidate; ++piCur) { const unsigned int dp = *piCur; // must have live triangles - if (piNumTriPtr[dp] > 0) { + if (piNumTriPtr[dp] > 0) { int priority = 0; // will the vertex be in cache, even after fanning occurs? unsigned int tmp; - if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= mConfigCacheDepth) { + if ((tmp = iStampCnt - piCachingStamps[dp]) + 2 * piNumTriPtr[dp] <= mConfigCacheDepth) { priority = tmp; } @@ -328,7 +333,7 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me while (!sDeadEndVStack.empty()) { unsigned int iCachedIdx = sDeadEndVStack.top(); sDeadEndVStack.pop(); - if (piNumTriPtr[ iCachedIdx ] > 0) { + if (piNumTriPtr[iCachedIdx] > 0) { ivdx = iCachedIdx; break; } @@ -337,9 +342,9 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me if (-1 == ivdx) { // well, there isn't such a vertex. Simply get the next vertex in input order and // hope it is not too bad ... - while (ics < (int)pMesh->mNumVertices) { + while (ics < (int)pMesh->mNumVertices) { ++ics; - if (piNumTriPtr[ics] > 0) { + if (piNumTriPtr[ics] > 0) { ivdx = ics; break; } @@ -349,29 +354,30 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me } ai_real fACMR2 = 0.0f; if (!DefaultLogger::isNullLogger()) { - fACMR2 = (float)iCacheMisses / pMesh->mNumFaces; - + fACMR2 = static_cast(iCacheMisses / pMesh->mNumFaces); + const ai_real averageACMR = ((fACMR - fACMR2) / fACMR) * 100.f; // very intense verbose logging ... prepare for much text if there are many meshes - if ( DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) { - ASSIMP_LOG_VERBOSE_DEBUG("Mesh %u | ACMR in: ", meshNum, " out: ", fACMR, " | ~", fACMR2, ((fACMR - fACMR2) / fACMR) * 100.f); + if (DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) { + ASSIMP_LOG_VERBOSE_DEBUG("Mesh ", meshNum, "| ACMR in: ", fACMR, " out: ", fACMR2, " | average ACMR ", averageACMR); } - fACMR2 *= pMesh->mNumFaces; } - // sort the output index buffer back to the input array - piCSIter = piIBOutput; - for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace) { - unsigned nind = pcFace->mNumIndices; - unsigned * ind = pcFace->mIndices; - if (nind > 0) ind[0] = *piCSIter++; - if (nind > 1) ind[1] = *piCSIter++; - if (nind > 2) ind[2] = *piCSIter++; - } - // delete temporary storage - delete[] piCachingStamps; - delete[] piIBOutput; - delete[] piCandidates; + // sort the output index buffer back to the input array + piCSIter = piIBOutput.begin(); + for (aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) { + unsigned nind = pcFace->mNumIndices; + unsigned *ind = pcFace->mIndices; + if (nind > 0) + ind[0] = *piCSIter++; + if (nind > 1) + ind[1] = *piCSIter++; + if (nind > 2) + ind[2] = *piCSIter++; + } return fACMR2; } + +} // namespace Assimp + diff --git a/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.h b/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.h index b2074a17c..30fa59608 100644 --- a/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.h +++ b/Engine/lib/assimp/code/PostProcessing/ImproveCacheLocality.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,8 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The ImproveCacheLocalityProcess reorders all faces for improved vertex @@ -61,26 +60,24 @@ namespace Assimp * * @note This step expects triagulated input data. */ -class ImproveCacheLocalityProcess : public BaseProcess -{ +class ImproveCacheLocalityProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ImproveCacheLocalityProcess(); - ~ImproveCacheLocalityProcess(); - -public: + ~ImproveCacheLocalityProcess() override = default; // ------------------------------------------------------------------- // Check whether the pp step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Executes the pp step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Configures the pp step - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; protected: // ------------------------------------------------------------------- diff --git a/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.cpp b/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.cpp index ef5999875..7d36fbf89 100644 --- a/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -53,15 +53,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -JoinVerticesProcess::JoinVerticesProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -JoinVerticesProcess::~JoinVerticesProcess() = default; // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. @@ -95,7 +90,7 @@ void JoinVerticesProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("JoinVerticesProcess finished "); return; } - + // Show statistics ASSIMP_LOG_INFO("JoinVerticesProcess finished | Verts in: ", iNumOldVertices, " out: ", iNumVertices, " | ~", @@ -105,55 +100,8 @@ void JoinVerticesProcess::Execute( aiScene* pScene) { namespace { -bool areVerticesEqual(const Vertex &lhs, const Vertex &rhs, bool complex) { - // A little helper to find locally close vertices faster. - // Try to reuse the lookup table from the last step. - const static float epsilon = 1e-5f; - // Squared because we check against squared length of the vector difference - static const float squareEpsilon = epsilon * epsilon; - - // Square compare is useful for animeshes vertices compare - if ((lhs.position - rhs.position).SquareLength() > squareEpsilon) { - return false; - } - - // We just test the other attributes even if they're not present in the mesh. - // In this case they're initialized to 0 so the comparison succeeds. - // By this method the non-present attributes are effectively ignored in the comparison. - if ((lhs.normal - rhs.normal).SquareLength() > squareEpsilon) { - return false; - } - - if ((lhs.texcoords[0] - rhs.texcoords[0]).SquareLength() > squareEpsilon) { - return false; - } - - if ((lhs.tangent - rhs.tangent).SquareLength() > squareEpsilon) { - return false; - } - - if ((lhs.bitangent - rhs.bitangent).SquareLength() > squareEpsilon) { - return false; - } - - // Usually we won't have vertex colors or multiple UVs, so we can skip from here - // Actually this increases runtime performance slightly, at least if branch - // prediction is on our side. - if (complex) { - for (int i = 0; i < 8; i++) { - if (i > 0 && (lhs.texcoords[i] - rhs.texcoords[i]).SquareLength() > squareEpsilon) { - return false; - } - if (GetColorDifference(lhs.colors[i], rhs.colors[i]) > squareEpsilon) { - return false; - } - } - } - return true; -} - template -void updateXMeshVertices(XMesh *pMesh, std::vector &uniqueVertices) { +void updateXMeshVertices(XMesh *pMesh, std::vector &uniqueVertices) { // replace vertex data with the unique data sets pMesh->mNumVertices = (unsigned int)uniqueVertices.size(); @@ -165,86 +113,55 @@ void updateXMeshVertices(XMesh *pMesh, std::vector &uniqueVertices) { // Position, if present (check made for aiAnimMesh) if (pMesh->mVertices) { - delete [] pMesh->mVertices; + std::unique_ptr oldVertices(pMesh->mVertices); pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; - for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { - pMesh->mVertices[a] = uniqueVertices[a].position; - } + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) + pMesh->mVertices[a] = oldVertices[uniqueVertices[a]]; } // Normals, if present if (pMesh->mNormals) { - delete [] pMesh->mNormals; + std::unique_ptr oldNormals(pMesh->mNormals); pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; - for( unsigned int a = 0; a < pMesh->mNumVertices; a++) { - pMesh->mNormals[a] = uniqueVertices[a].normal; - } + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) + pMesh->mNormals[a] = oldNormals[uniqueVertices[a]]; } // Tangents, if present if (pMesh->mTangents) { - delete [] pMesh->mTangents; + std::unique_ptr oldTangents(pMesh->mTangents); pMesh->mTangents = new aiVector3D[pMesh->mNumVertices]; - for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { - pMesh->mTangents[a] = uniqueVertices[a].tangent; - } + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) + pMesh->mTangents[a] = oldTangents[uniqueVertices[a]]; } // Bitangents as well if (pMesh->mBitangents) { - delete [] pMesh->mBitangents; + std::unique_ptr oldBitangents(pMesh->mBitangents); pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices]; - for (unsigned int a = 0; a < pMesh->mNumVertices; a++) { - pMesh->mBitangents[a] = uniqueVertices[a].bitangent; - } + for (unsigned int a = 0; a < pMesh->mNumVertices; a++) + pMesh->mBitangents[a] = oldBitangents[uniqueVertices[a]]; } // Vertex colors for (unsigned int a = 0; pMesh->HasVertexColors(a); a++) { - delete [] pMesh->mColors[a]; + std::unique_ptr oldColors(pMesh->mColors[a]); pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices]; - for( unsigned int b = 0; b < pMesh->mNumVertices; b++) { - pMesh->mColors[a][b] = uniqueVertices[b].colors[a]; - } + for (unsigned int b = 0; b < pMesh->mNumVertices; b++) + pMesh->mColors[a][b] = oldColors[uniqueVertices[b]]; } // Texture coords for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++) { - delete [] pMesh->mTextureCoords[a]; + std::unique_ptr oldTextureCoords(pMesh->mTextureCoords[a]); pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices]; - for (unsigned int b = 0; b < pMesh->mNumVertices; b++) { - pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a]; - } + for (unsigned int b = 0; b < pMesh->mNumVertices; b++) + pMesh->mTextureCoords[a][b] = oldTextureCoords[uniqueVertices[b]]; } } } // namespace // ------------------------------------------------------------------------------------------------ -// Unites identical vertices in the given mesh -// combine hashes -inline void hash_combine(std::size_t &) { - // empty -} -template -inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); - hash_combine(seed, rest...); -} -//template specialization for std::hash for Vertex -template<> -struct std::hash { - std::size_t operator()(Vertex const& v) const noexcept { - size_t seed = 0; - hash_combine(seed, v.position.x ,v.position.y,v.position.z); - return seed; - } -}; -//template specialization for std::equal_to for Vertex -template<> -struct std::equal_to { - bool operator()(const Vertex &lhs, const Vertex &rhs) const { - return areVerticesEqual(lhs, rhs, false); - } -}; +static constexpr size_t JOINED_VERTICES_MARK = 0x80000000u; + // now start the JoinVerticesProcess int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { static_assert( AI_MAX_NUMBER_OF_COLOR_SETS == 8, "AI_MAX_NUMBER_OF_COLOR_SETS == 8"); @@ -258,18 +175,17 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { // We should care only about used vertices, not all of them // (this can happen due to original file vertices buffer being used by // multiple meshes) - std::unordered_set usedVertexIndices; - usedVertexIndices.reserve(pMesh->mNumVertices); - for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { + std::vector usedVertexIndicesMask; + usedVertexIndicesMask.resize(pMesh->mNumVertices, false); + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { aiFace& face = pMesh->mFaces[a]; - for( unsigned int b = 0; b < face.mNumIndices; b++) { - usedVertexIndices.insert(face.mIndices[b]); + for (unsigned int b = 0; b < face.mNumIndices; b++) { + usedVertexIndicesMask[face.mIndices[b]] = true; } } // We'll never have more vertices afterwards. - std::vector uniqueVertices; - uniqueVertices.reserve( pMesh->mNumVertices); + std::vector uniqueVertices; // For each vertex the index of the vertex it was replaced by. // Since the maximal number of vertices is 2^31-1, the most significand bit can be used to mark @@ -279,52 +195,26 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { static_assert(AI_MAX_VERTICES == 0x7fffffff, "AI_MAX_VERTICES == 0x7fffffff"); std::vector replaceIndex( pMesh->mNumVertices, 0xffffffff); - // float posEpsilonSqr; - SpatialSort *vertexFinder = nullptr; - SpatialSort _vertexFinder; - - typedef std::pair SpatPair; - if (shared) { - std::vector* avf; - shared->GetProperty(AI_SPP_SPATIAL_SORT,avf); - if (avf) { - SpatPair& blubb = (*avf)[meshIndex]; - vertexFinder = &blubb.first; - // posEpsilonSqr = blubb.second; - } - } - if (!vertexFinder) { - // bad, need to compute it. - _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D)); - vertexFinder = &_vertexFinder; - // posEpsilonSqr = ComputePositionEpsilon(pMesh); - } - - // Again, better waste some bytes than a realloc ... - std::vector verticesFound; - verticesFound.reserve(10); - // Run an optimized code path if we don't have multiple UVs or vertex colors. // This should yield false in more than 99% of all imports ... const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0; // We'll never have more vertices afterwards. - std::vector> uniqueAnimatedVertices; + std::vector> uniqueAnimatedVertices; if (hasAnimMeshes) { uniqueAnimatedVertices.resize(pMesh->mNumAnimMeshes); for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { uniqueAnimatedVertices[animMeshIndex].reserve(pMesh->mNumVertices); } } - // a map that maps a vertix to its new index - std::unordered_map vertex2Index; + // a map that maps a vertex to its new index + std::map vertex2Index = {}; // we can not end up with more vertices than we started with - vertex2Index.reserve(pMesh->mNumVertices); // Now check each vertex if it brings something new to the table int newIndex = 0; for( unsigned int a = 0; a < pMesh->mNumVertices; a++) { // if the vertex is unused Do nothing - if (usedVertexIndices.find(a) == usedVertexIndices.end()) { + if (!usedVertexIndicesMask[a]) { continue; } // collect the vertex data @@ -334,19 +224,20 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { // if the vertex is not in the map then it is a new vertex add it. if (it == vertex2Index.end()) { // this is a new vertex give it a new index - vertex2Index[v] = newIndex; - //keep track of its index and increment 1 + vertex2Index.emplace(v, newIndex); + // keep track of its index and increment 1 replaceIndex[a] = newIndex++; // add the vertex to the unique vertices - uniqueVertices.push_back(v); + uniqueVertices.push_back(a); if (hasAnimMeshes) { for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) { - uniqueAnimatedVertices[animMeshIndex].emplace_back(pMesh->mAnimMeshes[animMeshIndex], a); + uniqueAnimatedVertices[animMeshIndex].emplace_back(a); } } } else{ // if the vertex is already there just find the replace index that is appropriate to it - replaceIndex[a] = it->second; + // mark it with JOINED_VERTICES_MARK + replaceIndex[a] = it->second | JOINED_VERTICES_MARK; } } @@ -375,7 +266,7 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { aiFace& face = pMesh->mFaces[a]; for( unsigned int b = 0; b < face.mNumIndices; b++) { - face.mIndices[b] = replaceIndex[face.mIndices[b]] & ~0x80000000; + face.mIndices[b] = replaceIndex[face.mIndices[b]] & ~JOINED_VERTICES_MARK; } } @@ -389,17 +280,8 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) { for ( unsigned int b = 0; b < bone->mNumWeights; b++ ) { const aiVertexWeight& ow = bone->mWeights[ b ]; // if the vertex is a unique one, translate it - if ( !( replaceIndex[ ow.mVertexId ] & 0x80000000 ) ) { - bool weightAlreadyExists = false; - for (std::vector::iterator vit = newWeights.begin(); vit != newWeights.end(); ++vit) { - if (vit->mVertexId == replaceIndex[ow.mVertexId]) { - weightAlreadyExists = true; - break; - } - } - if (weightAlreadyExists) { - continue; - } + // filter out joined vertices by JOINED_VERTICES_MARK. + if ( !( replaceIndex[ ow.mVertexId ] & JOINED_VERTICES_MARK ) ) { aiVertexWeight nw; nw.mVertexId = replaceIndex[ ow.mVertexId ]; nw.mWeight = ow.mWeight; diff --git a/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.h b/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.h index f95236e31..60630dae3 100644 --- a/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/JoinVerticesProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,8 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The JoinVerticesProcess unites identical vertices in all imported meshes. @@ -64,8 +63,10 @@ namespace Assimp */ class ASSIMP_API JoinVerticesProcess : public BaseProcess { public: - JoinVerticesProcess(); - ~JoinVerticesProcess(); + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + JoinVerticesProcess() = default; + ~JoinVerticesProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -73,14 +74,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Unites identical vertices in the given mesh. diff --git a/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp b/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp index 3192e07bc..71b6f9ec6 100644 --- a/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -36,13 +35,7 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** Implementation of the LimitBoneWeightsProcess post processing step */ - - +---------------------------------------------------------------------- */ #include "LimitBoneWeightsProcess.h" #include #include @@ -51,30 +44,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { + +// Make sure this value is set. +#ifndef AI_LMW_MAX_WEIGHTS +# define AI_LMW_MAX_WEIGHTS 16 +#endif // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -LimitBoneWeightsProcess::LimitBoneWeightsProcess() -{ - mMaxWeights = AI_LMW_MAX_WEIGHTS; +LimitBoneWeightsProcess::LimitBoneWeightsProcess() : + mMaxWeights(AI_LMW_MAX_WEIGHTS), mRemoveEmptyBones(true) { + // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -LimitBoneWeightsProcess::~LimitBoneWeightsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const -{ +bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_LimitBoneWeights) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void LimitBoneWeightsProcess::Execute( aiScene* pScene) -{ +void LimitBoneWeightsProcess::Execute( aiScene* pScene) { + ai_assert(pScene != nullptr); + ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin"); for (unsigned int m = 0; m < pScene->mNumMeshes; ++m) { @@ -86,16 +80,31 @@ void LimitBoneWeightsProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp) -{ - // get the current value of the property +void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp) { this->mMaxWeights = pImp->GetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS,AI_LMW_MAX_WEIGHTS); + this->mRemoveEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, 1) != 0; +} + +// ------------------------------------------------------------------------------------------------ +static unsigned int removeEmptyBones(aiMesh *pMesh) { + ai_assert(pMesh != nullptr); + + unsigned int writeBone = 0; + for (unsigned int readBone = 0; readBone< pMesh->mNumBones; ++readBone) { + aiBone* bone = pMesh->mBones[readBone]; + if (bone->mNumWeights > 0) { + pMesh->mBones[writeBone++] = bone; + } else { + delete bone; + } + } + + return writeBone; } // ------------------------------------------------------------------------------------------------ // Unites identical vertices in the given mesh -void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) -{ +void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) { if (!pMesh->HasBones()) return; @@ -105,11 +114,9 @@ void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) WeightsPerVertex vertexWeights(pMesh->mNumVertices); size_t maxVertexWeights = 0; - for (unsigned int b = 0; b < pMesh->mNumBones; ++b) - { + for (unsigned int b = 0; b < pMesh->mNumBones; ++b) { const aiBone* bone = pMesh->mBones[b]; - for (unsigned int w = 0; w < bone->mNumWeights; ++w) - { + for (unsigned int w = 0; w < bone->mNumWeights; ++w) { const aiVertexWeight& vw = bone->mWeights[w]; if (vertexWeights.size() <= vw.mVertexId) @@ -126,8 +133,7 @@ void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) unsigned int removed = 0, old_bones = pMesh->mNumBones; // now cut the weight count if it exceeds the maximum - for (WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit) - { + for (WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit) { if (vit->size() <= mMaxWeights) continue; @@ -154,40 +160,27 @@ void LimitBoneWeightsProcess::ProcessMesh(aiMesh* pMesh) } // clear weight count for all bone - for (unsigned int a = 0; a < pMesh->mNumBones; ++a) - { + for (unsigned int a = 0; a < pMesh->mNumBones; ++a) { pMesh->mBones[a]->mNumWeights = 0; } // rebuild the vertex weight array for all bones - for (unsigned int a = 0; a < vertexWeights.size(); ++a) - { + for (unsigned int a = 0; a < vertexWeights.size(); ++a) { const VertexWeightArray& vw = vertexWeights[a]; - for (const Weight* it = vw.begin(); it != vw.end(); ++it) - { + for (const Weight* it = vw.begin(); it != vw.end(); ++it) { aiBone* bone = pMesh->mBones[it->mBone]; bone->mWeights[bone->mNumWeights++] = aiVertexWeight(a, it->mWeight); } } // remove empty bones - unsigned int writeBone = 0; - - for (unsigned int readBone = 0; readBone< pMesh->mNumBones; ++readBone) - { - aiBone* bone = pMesh->mBones[readBone]; - if (bone->mNumWeights > 0) - { - pMesh->mBones[writeBone++] = bone; - } - else - { - delete bone; - } + if (mRemoveEmptyBones) { + pMesh->mNumBones = removeEmptyBones(pMesh); } - pMesh->mNumBones = writeBone; if (!DefaultLogger::isNullLogger()) { ASSIMP_LOG_INFO("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones); } } + +} // namespace Assimp diff --git a/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.h b/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.h index 22d286b68..b2612c313 100644 --- a/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/LimitBoneWeightsProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -74,8 +74,10 @@ namespace Assimp { */ class ASSIMP_API LimitBoneWeightsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. LimitBoneWeightsProcess(); - ~LimitBoneWeightsProcess(); + ~LimitBoneWeightsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. @@ -84,27 +86,27 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); - - // ------------------------------------------------------------------- - /** Limits the bone weight count for all vertices in the given mesh. - * @param pMesh The mesh to process. - */ - void ProcessMesh( aiMesh* pMesh); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; + + // ------------------------------------------------------------------- + /** Limits the bone weight count for all vertices in the given mesh. + * @param pMesh The mesh to process. + */ + void ProcessMesh( aiMesh* pMesh); // ------------------------------------------------------------------- /** Describes a bone weight on a vertex */ @@ -131,6 +133,7 @@ public: /** Maximum number of bones influencing any single vertex. */ unsigned int mMaxWeights; + bool mRemoveEmptyBones; }; } // end of namespace Assimp diff --git a/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.cpp b/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.cpp index 085979fe9..d0c5693e7 100644 --- a/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.cpp +++ b/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -49,10 +49,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -MakeVerboseFormatProcess::MakeVerboseFormatProcess() = default; -// ------------------------------------------------------------------------------------------------ -MakeVerboseFormatProcess::~MakeVerboseFormatProcess() = default; // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void MakeVerboseFormatProcess::Execute(aiScene *pScene) { @@ -93,8 +89,8 @@ bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh *pcMesh) { pvBitangents = new aiVector3D[iNumVerts]; } - aiVector3D *apvTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS] = { 0 }; - aiColor4D *apvColorSets[AI_MAX_NUMBER_OF_COLOR_SETS] = { 0 }; + aiVector3D *apvTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS] = { nullptr }; + aiColor4D *apvColorSets[AI_MAX_NUMBER_OF_COLOR_SETS] = { nullptr }; unsigned int p = 0; while (pcMesh->HasTextureCoords(p)) diff --git a/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.h b/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.h index 6b81da622..02fe21fa7 100644 --- a/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.h +++ b/Engine/lib/assimp/code/PostProcessing/MakeVerboseFormat.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -66,22 +66,19 @@ namespace Assimp { * The step has been added because it was required by the viewer, however * it has been moved to the main library since others might find it * useful, too. */ -class ASSIMP_API_WINONLY MakeVerboseFormatProcess : public BaseProcess -{ -public: - - - MakeVerboseFormatProcess(); - ~MakeVerboseFormatProcess(); - +class ASSIMP_API_WINONLY MakeVerboseFormatProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + MakeVerboseFormatProcess() = default; + ~MakeVerboseFormatProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. * @param pFlags The processing flags the importer was called with. A bitwise * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not */ - bool IsActive( unsigned int /*pFlags*/ ) const + bool IsActive( unsigned int /*pFlags*/ ) const override { // NOTE: There is no direct flag that corresponds to // this postprocess step. @@ -92,7 +89,7 @@ public: /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; public: diff --git a/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.cpp b/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.cpp index 26b06e9b6..01f6fca14 100644 --- a/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.cpp +++ b/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -78,10 +78,6 @@ OptimizeGraphProcess::OptimizeGraphProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -OptimizeGraphProcess::~OptimizeGraphProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const { @@ -168,7 +164,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n ++it; } if (join_master && !join.empty()) { - join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%u", count_merged++); + join_master->mName.length = ::ai_snprintf(join_master->mName.data, AI_MAXLEN, "$MergedNode_%u", count_merged++); unsigned int out_meshes = 0; for (std::list::const_iterator it = join.cbegin(); it != join.cend(); ++it) { diff --git a/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.h b/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.h index f5caa139c..c32748d7f 100644 --- a/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.h +++ b/Engine/lib/assimp/code/PostProcessing/OptimizeGraph.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -71,8 +71,10 @@ namespace Assimp { */ class OptimizeGraphProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. OptimizeGraphProcess(); - ~OptimizeGraphProcess(); + ~OptimizeGraphProcess() override = default; // ------------------------------------------------------------------- bool IsActive( unsigned int pFlags) const override; diff --git a/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.cpp b/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.cpp index a8c01e2d7..44792420c 100644 --- a/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.cpp +++ b/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team @@ -69,10 +69,6 @@ OptimizeMeshesProcess::OptimizeMeshesProcess() // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -OptimizeMeshesProcess::~OptimizeMeshesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool OptimizeMeshesProcess::IsActive( unsigned int pFlags) const diff --git a/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.h b/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.h index b80f98d5d..e424ae24a 100644 --- a/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.h +++ b/Engine/lib/assimp/code/PostProcessing/OptimizeMeshes.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,11 +68,10 @@ namespace Assimp { */ class OptimizeMeshesProcess : public BaseProcess { public: - /// @brief The class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. OptimizeMeshesProcess(); - - /// @brief The class destructor. - ~OptimizeMeshesProcess(); + ~OptimizeMeshesProcess() override = default; /** @brief Internal utility to store additional mesh info */ @@ -94,16 +93,14 @@ public: unsigned int output_id; }; -public: // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** @brief Specify whether you want meshes with different diff --git a/Engine/lib/assimp/code/PostProcessing/PretransformVertices.cpp b/Engine/lib/assimp/code/PostProcessing/PretransformVertices.cpp index 9ac90d277..0203ac211 100644 --- a/Engine/lib/assimp/code/PostProcessing/PretransformVertices.cpp +++ b/Engine/lib/assimp/code/PostProcessing/PretransformVertices.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -41,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/** @file PretransformVertices.cpp - * @brief Implementation of the "PretransformVertices" post processing step -*/ +/// @file PretransformVertices.cpp +/// @brief Implementation of the "PretransformVertices" post processing step #include "PretransformVertices.h" #include "ConvertToLHProcess.h" @@ -57,20 +54,44 @@ using namespace Assimp; #define AI_PTVS_VERTEX 0x0 #define AI_PTVS_FACE 0x1 +namespace { + +// Get a bitwise combination identifying the vertex format of a mesh +static unsigned int GetMeshVFormat(aiMesh *pcMesh) { + // the vertex format is stored in aiMesh::mBones for later retrieval. + // there isn't a good reason to compute it a few hundred times + // from scratch. The pointer is unused as animations are lost + // during PretransformVertices. + if (pcMesh->mBones) + return (unsigned int)(uint64_t)pcMesh->mBones; + + const unsigned int iRet = GetMeshVFormatUnique(pcMesh); + + // store the value for later use + pcMesh->mBones = (aiBone **)(uint64_t)iRet; + return iRet; +} + +// Get a list of all vertex formats that occur for a given material index +// The output list contains duplicate elements +static void GetVFormatList(const aiScene *pcScene, unsigned int iMat, std::list &aiOut) { + for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[i]; + if (iMat == pcMesh->mMaterialIndex) { + aiOut.push_back(GetMeshVFormat(pcMesh)); + } + } +} + +} // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer PretransformVertices::PretransformVertices() : - configKeepHierarchy(false), - configNormalize(false), - configTransform(false), - configTransformation(), - mConfigPointCloud(false) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -PretransformVertices::~PretransformVertices() = default; + mConfigKeepHierarchy(false), + mConfigNormalize(false), + mConfigTransform(false), + mConfigTransformation(), + mConfigPointCloud(false) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. @@ -83,11 +104,11 @@ bool PretransformVertices::IsActive(unsigned int pFlags) const { void PretransformVertices::SetupProperties(const Importer *pImp) { // Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE, // AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION - configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 0)); - configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 0)); - configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 0)); + mConfigKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 0)); + mConfigNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 0)); + mConfigTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 0)); - configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4()); + mConfigTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4()); mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); } @@ -103,25 +124,7 @@ unsigned int PretransformVertices::CountNodes(const aiNode *pcNode) const { } // ------------------------------------------------------------------------------------------------ -// Get a bitwise combination identifying the vertex format of a mesh -unsigned int PretransformVertices::GetMeshVFormat(aiMesh *pcMesh) const { - // the vertex format is stored in aiMesh::mBones for later retrieval. - // there isn't a good reason to compute it a few hundred times - // from scratch. The pointer is unused as animations are lost - // during PretransformVertices. - if (pcMesh->mBones) - return (unsigned int)(uint64_t)pcMesh->mBones; - - const unsigned int iRet = GetMeshVFormatUnique(pcMesh); - - // store the value for later use - pcMesh->mBones = (aiBone **)(uint64_t)iRet; - return iRet; -} - -// ------------------------------------------------------------------------------------------------ -// Count the number of vertices in the whole scene and a given -// material index +// Count the number of vertices in the whole scene and a given material index void PretransformVertices::CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat, unsigned int iVFormat, unsigned int *piFaces, unsigned int *piVertices) const { for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) { @@ -132,8 +135,7 @@ void PretransformVertices::CountVerticesAndFaces(const aiScene *pcScene, const a } } for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { - CountVerticesAndFaces(pcScene, pcNode->mChildren[i], iMat, - iVFormat, piFaces, piVertices); + CountVerticesAndFaces(pcScene, pcNode->mChildren[i], iMat, iVFormat, piFaces, piVertices); } } @@ -276,19 +278,6 @@ void PretransformVertices::CollectData(const aiScene *pcScene, const aiNode *pcN } } -// ------------------------------------------------------------------------------------------------ -// Get a list of all vertex formats that occur for a given material index -// The output list contains duplicate elements -void PretransformVertices::GetVFormatList(const aiScene *pcScene, unsigned int iMat, - std::list &aiOut) const { - for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { - aiMesh *pcMesh = pcScene->mMeshes[i]; - if (iMat == pcMesh->mMaterialIndex) { - aiOut.push_back(GetMeshVFormat(pcMesh)); - } - } -} - // ------------------------------------------------------------------------------------------------ // Compute the absolute transformation matrices of each node void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) { @@ -305,35 +294,37 @@ void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) { // Apply the node transformation to a mesh void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const { // Check whether we need to transform the coordinates at all - if (!mat.IsIdentity()) { + if (mat.IsIdentity()) { + return; + } - // Check for odd negative scale (mirror) - if (mesh->HasFaces() && mat.Determinant() < 0) { - // Reverse the mesh face winding order - FlipWindingOrderProcess::ProcessMesh(mesh); + // Check for odd negative scale (mirror) + if (mesh->HasFaces() && mat.Determinant() < 0) { + // Reverse the mesh face winding order + FlipWindingOrderProcess::ProcessMesh(mesh); + } + + // Update positions + if (mesh->HasPositions()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mVertices[i] = mat * mesh->mVertices[i]; } + } - // Update positions - if (mesh->HasPositions()) { + // Update normals and tangents + if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { + const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose(); + + if (mesh->HasNormals()) { for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { - mesh->mVertices[i] = mat * mesh->mVertices[i]; - } + mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); + } } - // Update normals and tangents - if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { - const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose(); - - if (mesh->HasNormals()) { - for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { - mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); - } - } - if (mesh->HasTangentsAndBitangents()) { - for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { - mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); - mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); - } + if (mesh->HasTangentsAndBitangents()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); + mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); } } } @@ -356,40 +347,41 @@ void PretransformVertices::BuildWCSMeshes(std::vector &out, aiMesh **i // yes, we can. mesh->mBones = reinterpret_cast(&node->mTransformation); mesh->mNumBones = UINT_MAX; - } else { + continue; + } - // try to find us in the list of newly created meshes - for (unsigned int n = 0; n < out.size(); ++n) { - aiMesh *ctz = out[n]; - if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast(ctz->mBones) == node->mTransformation) { + // try to find us in the list of newly created meshes + for (unsigned int n = 0; n < out.size(); ++n) { + aiMesh *ctz = out[n]; + if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast(ctz->mBones) == node->mTransformation) { - // ok, use this one. Update node mesh index - node->mMeshes[i] = numIn + n; - } + // ok, use this one. Update node mesh index + node->mMeshes[i] = numIn + n; } - if (node->mMeshes[i] < numIn) { - // Worst case. Need to operate on a full copy of the mesh - ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms"); - aiMesh *ntz; + } + if (node->mMeshes[i] < numIn) { + // Worst case. Need to operate on a full copy of the mesh + ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms"); + aiMesh *ntz; - const unsigned int tmp = mesh->mNumBones; // - mesh->mNumBones = 0; - SceneCombiner::Copy(&ntz, mesh); - mesh->mNumBones = tmp; + const unsigned int cacheNumBones = mesh->mNumBones; // + mesh->mNumBones = 0; + SceneCombiner::Copy(&ntz, mesh); + mesh->mNumBones = cacheNumBones; - ntz->mNumBones = node->mMeshes[i]; - ntz->mBones = reinterpret_cast(&node->mTransformation); + ntz->mNumBones = node->mMeshes[i]; + ntz->mBones = reinterpret_cast(&node->mTransformation); - out.push_back(ntz); + out.push_back(ntz); - node->mMeshes[i] = static_cast(numIn + out.size() - 1); - } + node->mMeshes[i] = static_cast(numIn + out.size() - 1); } } // call children - for (unsigned int i = 0; i < node->mNumChildren; ++i) + for (unsigned int i = 0; i < node->mNumChildren; ++i) { BuildWCSMeshes(out, in, numIn, node->mChildren[i]); + } } // ------------------------------------------------------------------------------------------------ @@ -398,8 +390,9 @@ void PretransformVertices::MakeIdentityTransform(aiNode *nd) const { nd->mTransformation = aiMatrix4x4(); // call children - for (unsigned int i = 0; i < nd->mNumChildren; ++i) + for (unsigned int i = 0; i < nd->mNumChildren; ++i) { MakeIdentityTransform(nd->mChildren[i]); + } } // ------------------------------------------------------------------------------------------------ @@ -409,8 +402,27 @@ void PretransformVertices::BuildMeshRefCountArray(const aiNode *nd, unsigned int refs[nd->mMeshes[i]]++; // call children - for (unsigned int i = 0; i < nd->mNumChildren; ++i) + for (unsigned int i = 0; i < nd->mNumChildren; ++i) { BuildMeshRefCountArray(nd->mChildren[i], refs); + } +} + +// ------------------------------------------------------------------------------------------------ +static void appendNewMeshesToScene(aiScene *pScene, std::vector &apcOutMeshes) { + ai_assert(pScene != nullptr); + + if (apcOutMeshes.empty()) { + return; + } + + aiMesh **npp = new aiMesh *[pScene->mNumMeshes + apcOutMeshes.size()]; + + ::memcpy(npp, pScene->mMeshes, sizeof(aiMesh *) * pScene->mNumMeshes); + ::memcpy(npp + pScene->mNumMeshes, &apcOutMeshes[0], sizeof(aiMesh *) * apcOutMeshes.size()); + + pScene->mNumMeshes += static_cast(apcOutMeshes.size()); + delete[] pScene->mMeshes; + pScene->mMeshes = npp; } // ------------------------------------------------------------------------------------------------ @@ -422,12 +434,12 @@ void PretransformVertices::Execute(aiScene *pScene) { if (!pScene->mNumMeshes) return; - const unsigned int iOldMeshes = pScene->mNumMeshes; - const unsigned int iOldAnimationChannels = pScene->mNumAnimations; - const unsigned int iOldNodes = CountNodes(pScene->mRootNode); + const unsigned int oldMeshes = pScene->mNumMeshes; + const unsigned int oldAnimationChannels = pScene->mNumAnimations; + const unsigned int oldNodes = CountNodes(pScene->mRootNode); - if (configTransform) { - pScene->mRootNode->mTransformation = configTransformation * pScene->mRootNode->mTransformation; + if (mConfigTransform) { + pScene->mRootNode->mTransformation = mConfigTransformation * pScene->mRootNode->mTransformation; } // first compute absolute transformation matrices for all nodes @@ -453,22 +465,13 @@ void PretransformVertices::Execute(aiScene *pScene) { // we go on and transform all meshes, if one is referenced by nodes // with different absolute transformations a depth copy of the mesh // is required. - if (configKeepHierarchy) { + if (mConfigKeepHierarchy) { // Hack: store the matrix we're transforming a mesh with in aiMesh::mBones BuildWCSMeshes(apcOutMeshes, pScene->mMeshes, pScene->mNumMeshes, pScene->mRootNode); // ... if new meshes have been generated, append them to the end of the scene - if (apcOutMeshes.size() > 0) { - aiMesh **npp = new aiMesh *[pScene->mNumMeshes + apcOutMeshes.size()]; - - memcpy(npp, pScene->mMeshes, sizeof(aiMesh *) * pScene->mNumMeshes); - memcpy(npp + pScene->mNumMeshes, &apcOutMeshes[0], sizeof(aiMesh *) * apcOutMeshes.size()); - - pScene->mNumMeshes += static_cast(apcOutMeshes.size()); - delete[] pScene->mMeshes; - pScene->mMeshes = npp; - } + appendNewMeshesToScene(pScene, apcOutMeshes); // now iterate through all meshes and transform them to world-space for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { @@ -492,34 +495,35 @@ void PretransformVertices::Execute(aiScene *pScene) { aiVFormats.sort(); aiVFormats.unique(); for (std::list::const_iterator j = aiVFormats.begin(); j != aiVFormats.end(); ++j) { - unsigned int iVertices = 0; - unsigned int iFaces = 0; - CountVerticesAndFaces(pScene, pScene->mRootNode, i, *j, &iFaces, &iVertices); - if (0 != iFaces && 0 != iVertices) { + unsigned int numVertices = 0u; + unsigned int numFaces = 0u; + CountVerticesAndFaces(pScene, pScene->mRootNode, i, *j, &numFaces, &numVertices); + if (0 != numFaces && 0 != numVertices) { apcOutMeshes.push_back(new aiMesh()); aiMesh *pcMesh = apcOutMeshes.back(); - pcMesh->mNumFaces = iFaces; - pcMesh->mNumVertices = iVertices; - pcMesh->mFaces = new aiFace[iFaces]; - pcMesh->mVertices = new aiVector3D[iVertices]; + pcMesh->mNumFaces = numFaces; + pcMesh->mNumVertices = numVertices; + pcMesh->mFaces = new aiFace[numFaces]; + pcMesh->mVertices = new aiVector3D[numVertices]; pcMesh->mMaterialIndex = i; - if ((*j) & 0x2) pcMesh->mNormals = new aiVector3D[iVertices]; + if ((*j) & 0x2) pcMesh->mNormals = new aiVector3D[numVertices]; if ((*j) & 0x4) { - pcMesh->mTangents = new aiVector3D[iVertices]; - pcMesh->mBitangents = new aiVector3D[iVertices]; + pcMesh->mTangents = new aiVector3D[numVertices]; + pcMesh->mBitangents = new aiVector3D[numVertices]; } - iFaces = 0; - while ((*j) & (0x100 << iFaces)) { - pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices]; - if ((*j) & (0x10000 << iFaces)) - pcMesh->mNumUVComponents[iFaces] = 3; - else - pcMesh->mNumUVComponents[iFaces] = 2; - iFaces++; + numFaces = 0; + while ((*j) & (0x100 << numFaces)) { + pcMesh->mTextureCoords[numFaces] = new aiVector3D[numVertices]; + if ((*j) & (0x10000 << numFaces)) { + pcMesh->mNumUVComponents[numFaces] = 3; + } else { + pcMesh->mNumUVComponents[numFaces] = 2; + } + ++numFaces; } - iFaces = 0; - while ((*j) & (0x1000000 << iFaces)) - pcMesh->mColors[iFaces++] = new aiColor4D[iVertices]; + numFaces = 0; + while ((*j) & (0x1000000 << numFaces)) + pcMesh->mColors[numFaces++] = new aiColor4D[numVertices]; // fill the mesh ... unsigned int aiTemp[2] = { 0, 0 }; @@ -581,7 +585,7 @@ void PretransformVertices::Execute(aiScene *pScene) { // multiply all properties of the camera with the absolute // transformation of the corresponding node cam->mPosition = nd->mTransformation * cam->mPosition; - cam->mLookAt = aiMatrix3x3(nd->mTransformation) * cam->mLookAt; + cam->mLookAt = nd->mTransformation * cam->mLookAt; cam->mUp = aiMatrix3x3(nd->mTransformation) * cam->mUp; } @@ -597,7 +601,7 @@ void PretransformVertices::Execute(aiScene *pScene) { l->mUp = aiMatrix3x3(nd->mTransformation) * l->mUp; } - if (!configKeepHierarchy) { + if (!mConfigKeepHierarchy) { // now delete all nodes in the scene and build a new // flat node graph with a root node and some level 1 children @@ -631,7 +635,7 @@ void PretransformVertices::Execute(aiScene *pScene) { aiNode *pcNode = new aiNode(); *nodes = pcNode; pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u", i); + pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "light_%u", i); pScene->mLights[i]->mName = pcNode->mName; } // generate camera nodes @@ -639,7 +643,7 @@ void PretransformVertices::Execute(aiScene *pScene) { aiNode *pcNode = new aiNode(); *nodes = pcNode; pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, MAXLEN, "cam_%u", i); + pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, AI_MAXLEN, "cam_%u", i); pScene->mCameras[i]->mName = pcNode->mName; } } @@ -648,7 +652,7 @@ void PretransformVertices::Execute(aiScene *pScene) { MakeIdentityTransform(pScene->mRootNode); } - if (configNormalize) { + if (mConfigNormalize) { // compute the boundary of all meshes aiVector3D min, max; MinMaxChooser()(min, max); @@ -678,9 +682,9 @@ void PretransformVertices::Execute(aiScene *pScene) { if (!DefaultLogger::isNullLogger()) { ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished"); - ASSIMP_LOG_INFO("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (", + ASSIMP_LOG_INFO("Removed ", oldNodes, " nodes and ", oldAnimationChannels, " animation channels (", CountNodes(pScene->mRootNode), " output nodes)"); ASSIMP_LOG_INFO("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras."); - ASSIMP_LOG_INFO("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")"); + ASSIMP_LOG_INFO("Moved ", oldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")"); } } diff --git a/Engine/lib/assimp/code/PostProcessing/PretransformVertices.h b/Engine/lib/assimp/code/PostProcessing/PretransformVertices.h index 14e5139ec..74c886488 100644 --- a/Engine/lib/assimp/code/PostProcessing/PretransformVertices.h +++ b/Engine/lib/assimp/code/PostProcessing/PretransformVertices.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -68,8 +68,10 @@ namespace Assimp { */ class ASSIMP_API PretransformVertices : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. PretransformVertices(); - ~PretransformVertices(); + ~PretransformVertices() override = default; // ------------------------------------------------------------------- // Check whether step is active @@ -88,7 +90,7 @@ public: * @param keep true for keep configuration. */ void KeepHierarchy(bool keep) { - configKeepHierarchy = keep; + mConfigKeepHierarchy = keep; } // ------------------------------------------------------------------- @@ -96,7 +98,7 @@ public: * @return ... */ bool IsHierarchyKept() const { - return configKeepHierarchy; + return mConfigKeepHierarchy; } private: @@ -106,7 +108,7 @@ private: // ------------------------------------------------------------------- // Get a bitwise combination identifying the vertex format of a mesh - unsigned int GetMeshVFormat(aiMesh *pcMesh) const; + //unsigned int GetMeshVFormat(aiMesh *pcMesh) const; // ------------------------------------------------------------------- // Count the number of vertices in the whole scene and a given @@ -129,8 +131,8 @@ private: // ------------------------------------------------------------------- // Get a list of all vertex formats that occur for a given material // The output list contains duplicate elements - void GetVFormatList(const aiScene *pcScene, unsigned int iMat, - std::list &aiOut) const; + /*void GetVFormatList(const aiScene *pcScene, unsigned int iMat, + std::list &aiOut) const;*/ // ------------------------------------------------------------------- // Compute the absolute transformation matrices of each node @@ -154,10 +156,10 @@ private: void BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const; //! Configuration option: keep scene hierarchy as long as possible - bool configKeepHierarchy; - bool configNormalize; - bool configTransform; - aiMatrix4x4 configTransformation; + bool mConfigKeepHierarchy; + bool mConfigNormalize; + bool mConfigTransform; + aiMatrix4x4 mConfigTransformation; bool mConfigPointCloud; }; diff --git a/Engine/lib/assimp/code/PostProcessing/ProcessHelper.cpp b/Engine/lib/assimp/code/PostProcessing/ProcessHelper.cpp index 15f01676c..cfbac3e80 100644 --- a/Engine/lib/assimp/code/PostProcessing/ProcessHelper.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ProcessHelper.cpp @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -52,8 +52,9 @@ namespace Assimp { // ------------------------------------------------------------------------------- void ConvertListToStrings(const std::string &in, std::list &out) { const char *s = in.c_str(); + const char *end = in.c_str() + in.size(); while (*s) { - SkipSpacesAndLineEnd(&s); + SkipSpacesAndLineEnd(&s, end); if (*s == '\'') { const char *base = ++s; while (*s != '\'') { @@ -66,7 +67,7 @@ void ConvertListToStrings(const std::string &in, std::list &out) { out.emplace_back(base, (size_t)(s - base)); ++s; } else { - out.push_back(GetNextToken(s)); + out.push_back(GetNextToken(s, end)); } } } @@ -175,10 +176,9 @@ unsigned int GetMeshVFormatUnique(const aiMesh *pcMesh) { // tangents and bitangents if (pcMesh->HasTangentsAndBitangents()) iRet |= 0x4; -#ifdef BOOST_STATIC_ASSERT - BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_COLOR_SETS); - BOOST_STATIC_ASSERT(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS); -#endif + + static_assert(8 >= AI_MAX_NUMBER_OF_COLOR_SETS, "static_assert(8 >= AI_MAX_NUMBER_OF_COLOR_SETS)"); + static_assert(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS, "static_assert(8 >= AI_MAX_NUMBER_OF_TEXTURECOORDS)"); // texture coordinates unsigned int p = 0; diff --git a/Engine/lib/assimp/code/PostProcessing/ProcessHelper.h b/Engine/lib/assimp/code/PostProcessing/ProcessHelper.h index 78df94ca1..273b122ae 100644 --- a/Engine/lib/assimp/code/PostProcessing/ProcessHelper.h +++ b/Engine/lib/assimp/code/PostProcessing/ProcessHelper.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. diff --git a/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp b/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp index 3c3cd59e0..111c233b1 100644 --- a/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp +++ b/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -45,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // internal headers - #include "RemoveRedundantMaterials.h" #include #include "ProcessHelper.h" @@ -57,164 +54,153 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -RemoveRedundantMatsProcess::RemoveRedundantMatsProcess() -: mConfigFixedMaterials() { - // nothing to do here -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -RemoveRedundantMatsProcess::~RemoveRedundantMatsProcess() = default; +RemoveRedundantMatsProcess::RemoveRedundantMatsProcess() : mConfigFixedMaterials() {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const -{ +bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_RemoveRedundantMaterials) != 0; } // ------------------------------------------------------------------------------------------------ // Setup import properties -void RemoveRedundantMatsProcess::SetupProperties(const Importer* pImp) -{ +void RemoveRedundantMatsProcess::SetupProperties(const Importer* pImp) { // Get value of AI_CONFIG_PP_RRM_EXCLUDE_LIST mConfigFixedMaterials = pImp->GetPropertyString(AI_CONFIG_PP_RRM_EXCLUDE_LIST,""); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void RemoveRedundantMatsProcess::Execute( aiScene* pScene) -{ +void RemoveRedundantMatsProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("RemoveRedundantMatsProcess begin"); unsigned int redundantRemoved = 0, unreferencedRemoved = 0; - if (pScene->mNumMaterials) - { - // Find out which materials are referenced by meshes - std::vector abReferenced(pScene->mNumMaterials,false); - for (unsigned int i = 0;i < pScene->mNumMeshes;++i) - abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true; + if (pScene->mNumMaterials == 0) { + return; + } + + // Find out which materials are referenced by meshes + std::vector abReferenced(pScene->mNumMaterials,false); + for (unsigned int i = 0;i < pScene->mNumMeshes;++i) { + abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true; + } - // If a list of materials to be excluded was given, match the list with - // our imported materials and 'salt' all positive matches to ensure that - // we get unique hashes later. - if (mConfigFixedMaterials.length()) { + // If a list of materials to be excluded was given, match the list with + // our imported materials and 'salt' all positive matches to ensure that + // we get unique hashes later. + if (mConfigFixedMaterials.length()) { + std::list strings; + ConvertListToStrings(mConfigFixedMaterials,strings); - std::list strings; - ConvertListToStrings(mConfigFixedMaterials,strings); + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { + aiMaterial* mat = pScene->mMaterials[i]; + ai_assert(mat != nullptr); + aiString name; + mat->Get(AI_MATKEY_NAME,name); - for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { - aiMaterial* mat = pScene->mMaterials[i]; + if (name.length != 0) { + std::list::const_iterator it = std::find(strings.begin(), strings.end(), name.data); + if (it != strings.end()) { + // Our brilliant 'salt': A single material property with ~ as first + // character to mark it as internal and temporary. + const int dummy = 1; + ((aiMaterial*)mat)->AddProperty(&dummy,1,"~RRM.UniqueMaterial",0,0); - aiString name; - mat->Get(AI_MATKEY_NAME,name); - - if (name.length) { - std::list::const_iterator it = std::find(strings.begin(), strings.end(), name.data); - if (it != strings.end()) { - - // Our brilliant 'salt': A single material property with ~ as first - // character to mark it as internal and temporary. - const int dummy = 1; - ((aiMaterial*)mat)->AddProperty(&dummy,1,"~RRM.UniqueMaterial",0,0); - - // Keep this material even if no mesh references it - abReferenced[i] = true; - ASSIMP_LOG_VERBOSE_DEBUG( "Found positive match in exclusion list: \'", name.data, "\'"); - } + // Keep this material even if no mesh references it + abReferenced[i] = true; + ASSIMP_LOG_VERBOSE_DEBUG( "Found positive match in exclusion list: \'", name.data, "\'"); } } } + } - // TODO: re-implement this algorithm to work in-place - unsigned int *aiMappingTable = new unsigned int[pScene->mNumMaterials]; - for ( unsigned int i=0; imNumMaterials; i++ ) { - aiMappingTable[ i ] = 0; + // TODO: re-implement this algorithm to work in-place + unsigned int *aiMappingTable = new unsigned int[pScene->mNumMaterials]; + for ( unsigned int i=0; imNumMaterials; i++ ) { + aiMappingTable[ i ] = 0; + } + unsigned int iNewNum = 0; + + // Iterate through all materials and calculate a hash for them + // store all hashes in a list and so a quick search whether + // we do already have a specific hash. This allows us to + // determine which materials are identical. + uint32_t *aiHashes = new uint32_t[ pScene->mNumMaterials ]; + for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { + // No mesh is referencing this material, remove it. + if (!abReferenced[i]) { + ++unreferencedRemoved; + delete pScene->mMaterials[i]; + pScene->mMaterials[i] = nullptr; + continue; } - unsigned int iNewNum = 0; - // Iterate through all materials and calculate a hash for them - // store all hashes in a list and so a quick search whether - // we do already have a specific hash. This allows us to - // determine which materials are identical. - uint32_t *aiHashes = new uint32_t[ pScene->mNumMaterials ];; - for (unsigned int i = 0; i < pScene->mNumMaterials;++i) - { - // No mesh is referencing this material, remove it. - if (!abReferenced[i]) { - ++unreferencedRemoved; + // Check all previously mapped materials for a matching hash. + // On a match we can delete this material and just make it ref to the same index. + uint32_t me = aiHashes[i] = ComputeMaterialHash(pScene->mMaterials[i]); + for (unsigned int a = 0; a < i;++a) { + if (abReferenced[a] && me == aiHashes[a]) { + ++redundantRemoved; + me = 0; + aiMappingTable[i] = aiMappingTable[a]; delete pScene->mMaterials[i]; pScene->mMaterials[i] = nullptr; + break; + } + } + // This is a new material that is referenced, add to the map. + if (me) { + aiMappingTable[i] = iNewNum++; + } + } + // If the new material count differs from the original, + // we need to rebuild the material list and remap mesh material indexes. + if (iNewNum < 1) { + delete [] aiMappingTable; + delete [] aiHashes; + pScene->mNumMaterials = 0; + return; + } + if (iNewNum != pScene->mNumMaterials) { + ai_assert(iNewNum > 0); + aiMaterial** ppcMaterials = new aiMaterial*[iNewNum]; + ::memset(ppcMaterials,0,sizeof(void*)*iNewNum); + for (unsigned int p = 0; p < pScene->mNumMaterials;++p) { + // if the material is not referenced ... remove it + if (!abReferenced[p]) { continue; } - // Check all previously mapped materials for a matching hash. - // On a match we can delete this material and just make it ref to the same index. - uint32_t me = aiHashes[i] = ComputeMaterialHash(pScene->mMaterials[i]); - for (unsigned int a = 0; a < i;++a) - { - if (abReferenced[a] && me == aiHashes[a]) { - ++redundantRemoved; - me = 0; - aiMappingTable[i] = aiMappingTable[a]; - delete pScene->mMaterials[i]; - pScene->mMaterials[i] = nullptr; - break; + // generate new names for modified materials that had no names + const unsigned int idx = aiMappingTable[p]; + if (ppcMaterials[idx]) { + aiString sz; + if( ppcMaterials[idx]->Get(AI_MATKEY_NAME, sz) != AI_SUCCESS ) { + sz.length = ::ai_snprintf(sz.data, AI_MAXLEN,"JoinedMaterial_#%u",p); + ((aiMaterial*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME); } - } - // This is a new material that is referenced, add to the map. - if (me) { - aiMappingTable[i] = iNewNum++; + } else { + ppcMaterials[idx] = pScene->mMaterials[p]; } } - // If the new material count differs from the original, - // we need to rebuild the material list and remap mesh material indexes. - if(iNewNum < 1) - throw DeadlyImportError("No materials remaining"); - if (iNewNum != pScene->mNumMaterials) { - ai_assert(iNewNum > 0); - aiMaterial** ppcMaterials = new aiMaterial*[iNewNum]; - ::memset(ppcMaterials,0,sizeof(void*)*iNewNum); - for (unsigned int p = 0; p < pScene->mNumMaterials;++p) - { - // if the material is not referenced ... remove it - if (!abReferenced[p]) { - continue; - } + // update all material indices + for (unsigned int p = 0; p < pScene->mNumMeshes;++p) { + aiMesh* mesh = pScene->mMeshes[p]; + ai_assert(nullptr != mesh); + mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex]; + } + // delete the old material list + delete[] pScene->mMaterials; + pScene->mMaterials = ppcMaterials; + pScene->mNumMaterials = iNewNum; + } + // delete temporary storage + delete[] aiHashes; + delete[] aiMappingTable; - // generate new names for modified materials that had no names - const unsigned int idx = aiMappingTable[p]; - if (ppcMaterials[idx]) { - aiString sz; - if( ppcMaterials[idx]->Get(AI_MATKEY_NAME, sz) != AI_SUCCESS ) { - sz.length = ::ai_snprintf(sz.data,MAXLEN,"JoinedMaterial_#%u",p); - ((aiMaterial*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME); - } - } else { - ppcMaterials[idx] = pScene->mMaterials[p]; - } - } - // update all material indices - for (unsigned int p = 0; p < pScene->mNumMeshes;++p) { - aiMesh* mesh = pScene->mMeshes[p]; - ai_assert(nullptr != mesh); - mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex]; - } - // delete the old material list - delete[] pScene->mMaterials; - pScene->mMaterials = ppcMaterials; - pScene->mNumMaterials = iNewNum; - } - // delete temporary storage - delete[] aiHashes; - delete[] aiMappingTable; - } - if (redundantRemoved == 0 && unreferencedRemoved == 0) - { + if (redundantRemoved == 0 && unreferencedRemoved == 0) { ASSIMP_LOG_DEBUG("RemoveRedundantMatsProcess finished "); - } - else - { + } else { ASSIMP_LOG_INFO("RemoveRedundantMatsProcess finished. Removed ", redundantRemoved, " redundant and ", unreferencedRemoved, " unused materials."); } diff --git a/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.h b/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.h index e8c1478fd..107de0daa 100644 --- a/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.h +++ b/Engine/lib/assimp/code/PostProcessing/RemoveRedundantMaterials.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -59,23 +59,22 @@ namespace Assimp { */ class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. RemoveRedundantMatsProcess(); - - /// The class destructor. - ~RemoveRedundantMatsProcess(); + ~RemoveRedundantMatsProcess() override = default; // ------------------------------------------------------------------- // Check whether step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup import settings - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** @brief Set list of fixed (inmutable) materials diff --git a/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.cpp b/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.cpp index 8bbe791f6..13ef81e23 100644 --- a/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -56,10 +54,6 @@ using namespace Assimp; RemoveVCProcess::RemoveVCProcess() : configDeleteFlags(), mScene() {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -RemoveVCProcess::~RemoveVCProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool RemoveVCProcess::IsActive(unsigned int pFlags) const { @@ -78,63 +72,6 @@ inline void ArrayDelete(T **&in, unsigned int &num) { num = 0; } -#if 0 -// ------------------------------------------------------------------------------------------------ -// Updates the node graph - removes all nodes which have the "remove" flag set and the -// "don't remove" flag not set. Nodes with meshes are never deleted. -bool UpdateNodeGraph(aiNode* node,std::list& childsOfParent,bool root) -{ - bool b = false; - - std::list mine; - for (unsigned int i = 0; i < node->mNumChildren;++i) - { - if(UpdateNodeGraph(node->mChildren[i],mine,false)) - b = true; - } - - // somewhat tricky ... mNumMeshes must be originally 0 and MSB2 may not be set, - // so we can do a simple comparison against MSB here - if (!root && AI_RC_UINT_MSB == node->mNumMeshes ) - { - // this node needs to be removed - if(node->mNumChildren) - { - childsOfParent.insert(childsOfParent.end(),mine.begin(),mine.end()); - - // set all children to nullptr to make sure they are not deleted when we delete ourself - for (unsigned int i = 0; i < node->mNumChildren;++i) - node->mChildren[i] = nullptr; - } - b = true; - delete node; - } - else - { - AI_RC_UNMASK(node->mNumMeshes); - childsOfParent.push_back(node); - - if (b) - { - // reallocate the array of our children here - node->mNumChildren = (unsigned int)mine.size(); - aiNode** const children = new aiNode*[mine.size()]; - aiNode** ptr = children; - - for (std::list::iterator it = mine.begin(), end = mine.end(); - it != end; ++it) - { - *ptr++ = *it; - } - delete[] node->mChildren; - node->mChildren = children; - return false; - } - } - return b; -} -#endif - // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void RemoveVCProcess::Execute(aiScene *pScene) { diff --git a/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.h b/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.h index cf1086882..8d9b5167a 100644 --- a/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/RemoveVCProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -58,11 +58,10 @@ namespace Assimp { */ class ASSIMP_API RemoveVCProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. RemoveVCProcess(); - - /// The class destructor. - ~RemoveVCProcess(); + ~RemoveVCProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -70,37 +69,35 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); + virtual void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Manually setup the configuration flags for the step * * @param Bitwise combination of the #aiComponent enumerated values. */ - void SetDeleteFlags(unsigned int f) - { + void SetDeleteFlags(unsigned int f) { configDeleteFlags = f; } // ------------------------------------------------------------------- /** Query the current configuration. */ - unsigned int GetDeleteFlags() const - { + unsigned int GetDeleteFlags() const { return configDeleteFlags; } diff --git a/Engine/lib/assimp/code/PostProcessing/ScaleProcess.cpp b/Engine/lib/assimp/code/PostProcessing/ScaleProcess.cpp index 34f68539a..5cd7eea6e 100644 --- a/Engine/lib/assimp/code/PostProcessing/ScaleProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ScaleProcess.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -47,25 +46,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -ScaleProcess::ScaleProcess() -: BaseProcess() -, mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { +// ------------------------------------------------------------------------------------------------ +ScaleProcess::ScaleProcess() : BaseProcess(), mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { + // empty } -ScaleProcess::~ScaleProcess() = default; - +// ------------------------------------------------------------------------------------------------ void ScaleProcess::setScale( ai_real scale ) { mScale = scale; } +// ------------------------------------------------------------------------------------------------ ai_real ScaleProcess::getScale() const { return mScale; } +// ------------------------------------------------------------------------------------------------ bool ScaleProcess::IsActive( unsigned int pFlags ) const { return ( pFlags & aiProcess_GlobalScale ) != 0; } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::SetupProperties( const Importer* pImp ) { // User scaling mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f ); @@ -78,14 +79,15 @@ void ScaleProcess::SetupProperties( const Importer* pImp ) { mScale *= importerScale; } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::Execute( aiScene* pScene ) { if(mScale == 1.0f) { return; // nothing to scale } - ai_assert( mScale != 0 ); - ai_assert( nullptr != pScene ); - ai_assert( nullptr != pScene->mRootNode ); + ai_assert(mScale != 0 ); + ai_assert(nullptr != pScene ); + ai_assert(nullptr != pScene->mRootNode ); if ( nullptr == pScene ) { return; @@ -96,37 +98,30 @@ void ScaleProcess::Execute( aiScene* pScene ) { } // Process animations and update position transform to new unit system - for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) - { + for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) { aiAnimation* animation = pScene->mAnimations[animationID]; - for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) - { + for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) { aiNodeAnim* anim = animation->mChannels[animationChannel]; - for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) - { + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) { aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; vectorKey.mValue *= mScale; } } } - for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) - { + for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) { aiMesh *mesh = pScene->mMeshes[meshID]; // Reconstruct mesh vertices to the new unit system - for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) - { + for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) { aiVector3D& vertex = mesh->mVertices[vertexID]; vertex *= mScale; } - // bone placement / scaling - for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) - { + for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) { // Reconstruct matrix by transform rather than by scale // This prevent scale values being changed which can // be meaningful in some cases @@ -144,7 +139,7 @@ void ScaleProcess::Execute( aiScene* pScene ) { aiMatrix4x4 scaling; aiMatrix4x4::Scaling( aiVector3D(scale), scaling ); - aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + const aiMatrix4x4 RotMatrix = aiMatrix4x4(rotation.GetMatrix()); bone->mOffsetMatrix = translation * RotMatrix * scaling; } @@ -152,12 +147,10 @@ void ScaleProcess::Execute( aiScene* pScene ) { // animation mesh processing // convert by position rather than scale. - for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) - { + for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) { aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; - for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) - { + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) { aiVector3D& vertex = animMesh->mVertices[vertexID]; vertex *= mScale; } @@ -167,16 +160,17 @@ void ScaleProcess::Execute( aiScene* pScene ) { traverseNodes( pScene->mRootNode ); } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { applyScaling( node ); - for( size_t i = 0; i < node->mNumChildren; i++) - { + for( size_t i = 0; i < node->mNumChildren; i++) { // recurse into the tree until we are done! traverseNodes( node->mChildren[i], nested_node_id+1 ); } } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::applyScaling( aiNode *currentNode ) { if ( nullptr != currentNode ) { // Reconstruct matrix by transform rather than by scale diff --git a/Engine/lib/assimp/code/PostProcessing/ScaleProcess.h b/Engine/lib/assimp/code/PostProcessing/ScaleProcess.h index b6eb75de7..2887c7221 100644 --- a/Engine/lib/assimp/code/PostProcessing/ScaleProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/ScaleProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -62,11 +62,10 @@ namespace Assimp { */ class ASSIMP_API ScaleProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ScaleProcess(); - - /// The class destructor. - virtual ~ScaleProcess(); + ~ScaleProcess() override = default; /// Will set the scale manually. void setScale( ai_real scale ); @@ -75,13 +74,13 @@ public: ai_real getScale() const; /// Overwritten, @see BaseProcess - virtual bool IsActive( unsigned int pFlags ) const; + virtual bool IsActive( unsigned int pFlags ) const override; /// Overwritten, @see BaseProcess - virtual void SetupProperties( const Importer* pImp ); + virtual void SetupProperties( const Importer* pImp ) override; /// Overwritten, @see BaseProcess - virtual void Execute( aiScene* pScene ); + virtual void Execute( aiScene* pScene ) override; private: void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 ); diff --git a/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.cpp b/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.cpp index 6312fa173..76b685d58 100644 --- a/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -54,14 +52,7 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -SortByPTypeProcess::SortByPTypeProcess() : - mConfigRemoveMeshes(0) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -SortByPTypeProcess::~SortByPTypeProcess() = default; +SortByPTypeProcess::SortByPTypeProcess() : mConfigRemoveMeshes(0) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. @@ -74,42 +65,53 @@ void SortByPTypeProcess::SetupProperties(const Importer *pImp) { mConfigRemoveMeshes = pImp->GetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, 0); } +// ------------------------------------------------------------------------------------------------ +static void clearMeshesInNode(aiNode *node) { + delete[] node->mMeshes; + node->mNumMeshes = 0; + node->mMeshes = nullptr; +} + // ------------------------------------------------------------------------------------------------ // Update changed meshes in all nodes void UpdateNodes(const std::vector &replaceMeshIndex, aiNode *node) { + ai_assert(node != nullptr); + if (node->mNumMeshes) { unsigned int newSize = 0; for (unsigned int m = 0; m < node->mNumMeshes; ++m) { unsigned int add = node->mMeshes[m] << 2; for (unsigned int i = 0; i < 4; ++i) { - if (UINT_MAX != replaceMeshIndex[add + i]) ++newSize; - } - } - if (!newSize) { - delete[] node->mMeshes; - node->mNumMeshes = 0; - node->mMeshes = nullptr; - } else { - // Try to reuse the old array if possible - unsigned int *newMeshes = (newSize > node->mNumMeshes ? new unsigned int[newSize] : node->mMeshes); - - for (unsigned int m = 0; m < node->mNumMeshes; ++m) { - unsigned int add = node->mMeshes[m] << 2; - for (unsigned int i = 0; i < 4; ++i) { - if (UINT_MAX != replaceMeshIndex[add + i]) - *newMeshes++ = replaceMeshIndex[add + i]; + if (UINT_MAX != replaceMeshIndex[add + i]) { + ++newSize; } } - if (newSize > node->mNumMeshes) - delete[] node->mMeshes; - - node->mMeshes = newMeshes - (node->mNumMeshes = newSize); } + if (newSize == 0) { + clearMeshesInNode(node); + return; + } + + // Try to reuse the old array if possible + unsigned int *newMeshes = (newSize > node->mNumMeshes ? new unsigned int[newSize] : node->mMeshes); + for (unsigned int m = 0; m < node->mNumMeshes; ++m) { + unsigned int add = node->mMeshes[m] << 2; + for (unsigned int i = 0; i < 4; ++i) { + if (UINT_MAX != replaceMeshIndex[add + i]) { + *newMeshes++ = replaceMeshIndex[add + i]; + } + } + } + if (newSize > node->mNumMeshes) { + clearMeshesInNode(node); + } + node->mMeshes = newMeshes - (node->mNumMeshes = newSize); } // call all subnodes recursively - for (unsigned int m = 0; m < node->mNumChildren; ++m) + for (unsigned int m = 0; m < node->mNumChildren; ++m) { UpdateNodes(replaceMeshIndex, node->mChildren[m]); + } } // ------------------------------------------------------------------------------------------------ @@ -159,7 +161,7 @@ void SortByPTypeProcess::Execute(aiScene *pScene) { if (1 == num) { if (!(mConfigRemoveMeshes & mesh->mPrimitiveTypes)) { *meshIdx = static_cast(outMeshes.size()); - outMeshes.push_back(mesh); + outMeshes.emplace_back(mesh); } else { delete mesh; pScene->mMeshes[i] = nullptr; @@ -175,6 +177,10 @@ void SortByPTypeProcess::Execute(aiScene *pScene) { // with the largest number of primitives unsigned int aiNumPerPType[4] = { 0, 0, 0, 0 }; aiFace *pFirstFace = mesh->mFaces; + if (pFirstFace == nullptr) { + continue; + } + aiFace *const pLastFace = pFirstFace + mesh->mNumFaces; unsigned int numPolyVerts = 0; @@ -315,21 +321,23 @@ void SortByPTypeProcess::Execute(aiScene *pScene) { if (vert) { *vert++ = mesh->mVertices[idx]; - //mesh->mVertices[idx].x = get_qnan(); } - if (nor) *nor++ = mesh->mNormals[idx]; + if (nor) + *nor++ = mesh->mNormals[idx]; if (tan) { *tan++ = mesh->mTangents[idx]; *bit++ = mesh->mBitangents[idx]; } for (unsigned int pp = 0; pp < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++pp) { - if (!uv[pp]) break; + if (!uv[pp]) + break; *uv[pp]++ = mesh->mTextureCoords[pp][idx]; } for (unsigned int pp = 0; pp < AI_MAX_NUMBER_OF_COLOR_SETS; ++pp) { - if (!cols[pp]) break; + if (!cols[pp]) + break; *cols[pp]++ = mesh->mColors[pp][idx]; } @@ -355,7 +363,7 @@ void SortByPTypeProcess::Execute(aiScene *pScene) { } } if (pp == mesh->mNumAnimMeshes) - amIdx++; + ++amIdx; in.mIndices[q] = outIdx++; } diff --git a/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.h b/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.h index e30342a86..aa7774d7f 100644 --- a/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/SortByPTypeProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,17 +60,19 @@ namespace Assimp { */ class ASSIMP_API SortByPTypeProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SortByPTypeProcess(); - ~SortByPTypeProcess(); + ~SortByPTypeProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; private: int mConfigRemoveMeshes; diff --git a/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp b/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp index a501d3bd6..f63478767 100644 --- a/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,7 +39,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - /// @file SplitByBoneCountProcess.cpp /// Implementation of the SplitByBoneCount postprocessing step @@ -59,47 +57,34 @@ using namespace Assimp::Formatter; // ------------------------------------------------------------------------------------------------ // Constructor -SplitByBoneCountProcess::SplitByBoneCountProcess() -{ - // set default, might be overridden by importer config - mMaxBoneCount = AI_SBBC_DEFAULT_MAX_BONES; -} - -// ------------------------------------------------------------------------------------------------ -// Destructor -SplitByBoneCountProcess::~SplitByBoneCountProcess() = default; +SplitByBoneCountProcess::SplitByBoneCountProcess() : mMaxBoneCount(AI_SBBC_DEFAULT_MAX_BONES) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag. -bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const -{ +bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const { return !!(pFlags & aiProcess_SplitByBoneCount); } // ------------------------------------------------------------------------------------------------ // Updates internal properties -void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) -{ +void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) { mMaxBoneCount = pImp->GetPropertyInteger(AI_CONFIG_PP_SBBC_MAX_BONES,AI_SBBC_DEFAULT_MAX_BONES); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void SplitByBoneCountProcess::Execute( aiScene* pScene) -{ +void SplitByBoneCountProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("SplitByBoneCountProcess begin"); // early out bool isNecessary = false; for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) - if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) - { + if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) { isNecessary = true; break; } - if( !isNecessary ) - { + if( !isNecessary ) { ASSIMP_LOG_DEBUG("SplitByBoneCountProcess early-out: no meshes with more than ", mMaxBoneCount, " bones." ); return; } @@ -111,28 +96,23 @@ void SplitByBoneCountProcess::Execute( aiScene* pScene) // build a new array of meshes for the scene std::vector meshes; - for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) - { + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) { aiMesh* srcMesh = pScene->mMeshes[a]; std::vector newMeshes; SplitMesh( pScene->mMeshes[a], newMeshes); // mesh was split - if( !newMeshes.empty() ) - { + if( !newMeshes.empty() ) { // store new meshes and indices of the new meshes - for( unsigned int b = 0; b < newMeshes.size(); ++b) - { + for( unsigned int b = 0; b < newMeshes.size(); ++b) { mSubMeshIndices[a].push_back( static_cast(meshes.size())); meshes.push_back( newMeshes[b]); } // and destroy the source mesh. It should be completely contained inside the new submeshes delete srcMesh; - } - else - { + } else { // Mesh is kept unchanged - store it's new place in the mesh array mSubMeshIndices[a].push_back( static_cast(meshes.size())); meshes.push_back( srcMesh); @@ -153,11 +133,9 @@ void SplitByBoneCountProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Splits the given mesh by bone count. -void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& poNewMeshes) const -{ +void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& poNewMeshes) const { // skip if not necessary - if( pMesh->mNumBones <= mMaxBoneCount ) - { + if( pMesh->mNumBones <= mMaxBoneCount ) { return; } @@ -165,42 +143,35 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector BoneWeight; std::vector< std::vector > vertexBones( pMesh->mNumVertices); - for( unsigned int a = 0; a < pMesh->mNumBones; ++a) - { + for( unsigned int a = 0; a < pMesh->mNumBones; ++a) { const aiBone* bone = pMesh->mBones[a]; - for( unsigned int b = 0; b < bone->mNumWeights; ++b) - { - if (bone->mWeights[b].mWeight > 0.0f) - { - int vertexId = bone->mWeights[b].mVertexId; - vertexBones[vertexId].emplace_back(a, bone->mWeights[b].mWeight); - if (vertexBones[vertexId].size() > mMaxBoneCount) - { - throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!"); + for( unsigned int b = 0; b < bone->mNumWeights; ++b) { + if (bone->mWeights[b].mWeight > 0.0f) { + int vertexId = bone->mWeights[b].mVertexId; + vertexBones[vertexId].emplace_back(a, bone->mWeights[b].mWeight); + if (vertexBones[vertexId].size() > mMaxBoneCount) { + throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!"); + } } - } } } unsigned int numFacesHandled = 0; std::vector isFaceHandled( pMesh->mNumFaces, false); - while( numFacesHandled < pMesh->mNumFaces ) - { + while( numFacesHandled < pMesh->mNumFaces ) { // which bones are used in the current submesh unsigned int numBones = 0; std::vector isBoneUsed( pMesh->mNumBones, false); // indices of the faces which are going to go into this submesh - std::vector subMeshFaces; + IndexArray subMeshFaces; subMeshFaces.reserve( pMesh->mNumFaces); // accumulated vertex count of all the faces in this submesh unsigned int numSubMeshVertices = 0; // add faces to the new submesh as long as all bones affecting the faces' vertices fit in the limit - for( unsigned int a = 0; a < pMesh->mNumFaces; ++a) - { + for( unsigned int a = 0; a < pMesh->mNumFaces; ++a) { // skip if the face is already stored in a submesh - if( isFaceHandled[a] ) - { + if( isFaceHandled[a] ) { continue; } // a small local set of new bones for the current face. State of all used bones for that face @@ -209,33 +180,27 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormFaces[a]; // check every vertex if its bones would still fit into the current submesh - for( unsigned int b = 0; b < face.mNumIndices; ++b ) - { - const std::vector& vb = vertexBones[face.mIndices[b]]; - for( unsigned int c = 0; c < vb.size(); ++c) - { - unsigned int boneIndex = vb[c].first; - if( !isBoneUsed[boneIndex] ) - { - newBonesAtCurrentFace.insert(boneIndex); + for( unsigned int b = 0; b < face.mNumIndices; ++b ) { + const std::vector& vb = vertexBones[face.mIndices[b]]; + for( unsigned int c = 0; c < vb.size(); ++c) { + unsigned int boneIndex = vb[c].first; + if( !isBoneUsed[boneIndex] ) { + newBonesAtCurrentFace.insert(boneIndex); + } } - } } // leave out the face if the new bones required for this face don't fit the bone count limit anymore - if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) - { + if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) { continue; } // mark all new bones as necessary - for (std::set::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) - { - if (!isBoneUsed[*it]) - { - isBoneUsed[*it] = true; - numBones++; - } + for (std::set::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) { + if (!isBoneUsed[*it]) { + isBoneUsed[*it] = true; + ++numBones; + } } // store the face index and the vertex count @@ -244,44 +209,37 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormName.length > 0 ) - { + if( pMesh->mName.length > 0 ) { newMesh->mName.Set( format() << pMesh->mName.data << "_sub" << poNewMeshes.size()); } newMesh->mMaterialIndex = pMesh->mMaterialIndex; newMesh->mPrimitiveTypes = pMesh->mPrimitiveTypes; - poNewMeshes.push_back( newMesh); + poNewMeshes.emplace_back( newMesh); // create all the arrays for this mesh if the old mesh contained them newMesh->mNumVertices = numSubMeshVertices; newMesh->mNumFaces = static_cast(subMeshFaces.size()); newMesh->mVertices = new aiVector3D[newMesh->mNumVertices]; - if( pMesh->HasNormals() ) - { + if( pMesh->HasNormals() ) { newMesh->mNormals = new aiVector3D[newMesh->mNumVertices]; } - if( pMesh->HasTangentsAndBitangents() ) - { + if( pMesh->HasTangentsAndBitangents() ) { newMesh->mTangents = new aiVector3D[newMesh->mNumVertices]; newMesh->mBitangents = new aiVector3D[newMesh->mNumVertices]; } - for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) - { - if( pMesh->HasTextureCoords( a) ) - { + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { + if( pMesh->HasTextureCoords( a) ) { newMesh->mTextureCoords[a] = new aiVector3D[newMesh->mNumVertices]; } newMesh->mNumUVComponents[a] = pMesh->mNumUVComponents[a]; } - for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) - { - if( pMesh->HasVertexColors( a) ) - { + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) { + if( pMesh->HasVertexColors( a) ) { newMesh->mColors[a] = new aiColor4D[newMesh->mNumVertices]; } } @@ -289,42 +247,34 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormFaces = new aiFace[subMeshFaces.size()]; unsigned int nvi = 0; // next vertex index - std::vector previousVertexIndices( numSubMeshVertices, std::numeric_limits::max()); // per new vertex: its index in the source mesh - for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) - { + IndexArray previousVertexIndices( numSubMeshVertices, std::numeric_limits::max()); // per new vertex: its index in the source mesh + for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) { const aiFace& srcFace = pMesh->mFaces[subMeshFaces[a]]; aiFace& dstFace = newMesh->mFaces[a]; dstFace.mNumIndices = srcFace.mNumIndices; dstFace.mIndices = new unsigned int[dstFace.mNumIndices]; // accumulate linearly all the vertices of the source face - for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) - { + for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) { unsigned int srcIndex = srcFace.mIndices[b]; dstFace.mIndices[b] = nvi; previousVertexIndices[nvi] = srcIndex; newMesh->mVertices[nvi] = pMesh->mVertices[srcIndex]; - if( pMesh->HasNormals() ) - { + if( pMesh->HasNormals() ) { newMesh->mNormals[nvi] = pMesh->mNormals[srcIndex]; } - if( pMesh->HasTangentsAndBitangents() ) - { + if( pMesh->HasTangentsAndBitangents() ) { newMesh->mTangents[nvi] = pMesh->mTangents[srcIndex]; newMesh->mBitangents[nvi] = pMesh->mBitangents[srcIndex]; } - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) - { - if( pMesh->HasTextureCoords( c) ) - { + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) { + if( pMesh->HasTextureCoords( c) ) { newMesh->mTextureCoords[c][nvi] = pMesh->mTextureCoords[c][srcIndex]; } } - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) - { - if( pMesh->HasVertexColors( c) ) - { + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) { + if( pMesh->HasVertexColors( c) ) { newMesh->mColors[c][nvi] = pMesh->mColors[c][srcIndex]; } } @@ -340,10 +290,8 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormBones = new aiBone*[numBones]; std::vector mappedBoneIndex( pMesh->mNumBones, std::numeric_limits::max()); - for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) - { - if( !isBoneUsed[a] ) - { + for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) { + if( !isBoneUsed[a] ) { continue; } @@ -360,24 +308,20 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumBones == numBones ); // iterate over all new vertices and count which bones affected its old vertex in the source mesh - for( unsigned int a = 0; a < numSubMeshVertices; ++a ) - { + for( unsigned int a = 0; a < numSubMeshVertices; ++a ) { unsigned int oldIndex = previousVertexIndices[a]; const std::vector& bonesOnThisVertex = vertexBones[oldIndex]; - for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) - { + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) { unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; - if( newBoneIndex != std::numeric_limits::max() ) - { + if( newBoneIndex != std::numeric_limits::max() ) { newMesh->mBones[newBoneIndex]->mNumWeights++; } } } // allocate all bone weight arrays accordingly - for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) - { + for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) { aiBone* bone = newMesh->mBones[a]; ai_assert( bone->mNumWeights > 0 ); bone->mWeights = new aiVertexWeight[bone->mNumWeights]; @@ -385,16 +329,14 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& bonesOnThisVertex = vertexBones[previousIndex]; // all of the bones affecting it should be present in the new submesh, or else // the face it comprises shouldn't be present - for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) - { + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) { unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; ai_assert( newBoneIndex != std::numeric_limits::max() ); aiVertexWeight* dstWeight = newMesh->mBones[newBoneIndex]->mWeights + newMesh->mBones[newBoneIndex]->mNumWeights; @@ -450,16 +392,13 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumMeshes > 0 ) - { - std::vector newMeshList; - for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) - { + if( pNode->mNumMeshes != 0 ) { + IndexArray newMeshList; + for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) { unsigned int srcIndex = pNode->mMeshes[a]; - const std::vector& replaceMeshes = mSubMeshIndices[srcIndex]; + const IndexArray& replaceMeshes = mSubMeshIndices[srcIndex]; newMeshList.insert( newMeshList.end(), replaceMeshes.begin(), replaceMeshes.end()); } @@ -470,8 +409,7 @@ void SplitByBoneCountProcess::UpdateNode( aiNode* pNode) const } // do that also recursively for all children - for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) - { + for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) { UpdateNode( pNode->mChildren[a]); } } diff --git a/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.h b/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.h index 938b00c7f..c90661cb5 100644 --- a/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/SplitByBoneCountProcess.h @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -51,9 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace Assimp -{ - +namespace Assimp { /** Postprocessing filter to split meshes with many bones into submeshes * so that each submesh has a certain max bone count. @@ -61,34 +58,33 @@ namespace Assimp * Applied BEFORE the JoinVertices-Step occurs. * Returns NON-UNIQUE vertices, splits by bone count. */ -class SplitByBoneCountProcess : public BaseProcess -{ +class SplitByBoneCountProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SplitByBoneCountProcess(); - ~SplitByBoneCountProcess(); + ~SplitByBoneCountProcess() override = default; -public: - /** Returns whether the processing step is present in the given flag. - * @param pFlags The processing flags the importer was called with. A - * bitwise combination of #aiPostProcessSteps. - * @return true if the process is present in this flag fields, - * false if not. - */ - bool IsActive( unsigned int pFlags) const; + /// @brief Returns whether the processing step is present in the given flag. + /// @param pFlags The processing flags the importer was called with. A + /// bitwise combination of #aiPostProcessSteps. + /// @return true if the process is present in this flag fields, false if not. + bool IsActive( unsigned int pFlags) const override; - /** Called prior to ExecuteOnScene(). - * The function is a request to the process to update its configuration - * basing on the Importer's configuration property list. - */ - virtual void SetupProperties(const Importer* pImp); + /// @brief Called prior to ExecuteOnScene(). + /// The function is a request to the process to update its configuration + /// basing on the Importer's configuration property list. + virtual void SetupProperties(const Importer* pImp) override; + + /// @brief Will return the maximal number of bones. + /// @return The maximal number of bones. + size_t getMaxNumberOfBones() const; protected: - /** Executes the post processing step on the given imported data. - * At the moment a process is not supposed to fail. - * @param pScene The imported data to work at. - */ - void Execute( aiScene* pScene); + /// Executes the post processing step on the given imported data. + /// At the moment a process is not supposed to fail. + /// @param pScene The imported data to work at. + void Execute( aiScene* pScene) override; /// Splits the given mesh by bone count. /// @param pMesh the Mesh to split. Is not changed at all, but might be superfluous in case it was split. @@ -98,14 +94,19 @@ protected: /// Recursively updates the node's mesh list to account for the changed mesh list void UpdateNode( aiNode* pNode) const; -public: +private: /// Max bone count. Splitting occurs if a mesh has more than that number of bones. size_t mMaxBoneCount; /// Per mesh index: Array of indices of the new submeshes. - std::vector< std::vector > mSubMeshIndices; + using IndexArray = std::vector; + std::vector mSubMeshIndices; }; +inline size_t SplitByBoneCountProcess::getMaxNumberOfBones() const { + return mMaxBoneCount; +} + } // end of namespace Assimp #endif // !!AI_SPLITBYBONECOUNTPROCESS_H_INC diff --git a/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.cpp b/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.cpp index 151ac4991..cb9727651 100644 --- a/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.cpp +++ b/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -40,9 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** - * @file Implementation of the SplitLargeMeshes postprocessing step - */ + /// @file Implementation of the SplitLargeMeshes postprocessing step // internal headers of the post-processing framework #include "SplitLargeMeshes.h" @@ -55,9 +52,6 @@ SplitLargeMeshesProcess_Triangle::SplitLargeMeshesProcess_Triangle() { LIMIT = AI_SLM_DEFAULT_MAX_TRIANGLES; } -// ------------------------------------------------------------------------------------------------ -SplitLargeMeshesProcess_Triangle::~SplitLargeMeshesProcess_Triangle() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool SplitLargeMeshesProcess_Triangle::IsActive( unsigned int pFlags) const { @@ -78,22 +72,22 @@ void SplitLargeMeshesProcess_Triangle::Execute( aiScene* pScene) { this->SplitMesh(a, pScene->mMeshes[a],avList); } - if (avList.size() != pScene->mNumMeshes) { - // it seems something has been split. rebuild the mesh list - delete[] pScene->mMeshes; - pScene->mNumMeshes = (unsigned int)avList.size(); - pScene->mMeshes = new aiMesh*[avList.size()]; - - for (unsigned int i = 0; i < avList.size();++i) { - pScene->mMeshes[i] = avList[i].first; - } - - // now we need to update all nodes - this->UpdateNode(pScene->mRootNode,avList); - ASSIMP_LOG_INFO("SplitLargeMeshesProcess_Triangle finished. Meshes have been split"); - } else { + if (avList.size() == pScene->mNumMeshes) { ASSIMP_LOG_DEBUG("SplitLargeMeshesProcess_Triangle finished. There was nothing to do"); } + + // it seems something has been split. rebuild the mesh list + delete[] pScene->mMeshes; + pScene->mNumMeshes = (unsigned int)avList.size(); + pScene->mMeshes = new aiMesh*[avList.size()]; + + for (unsigned int i = 0; i < avList.size();++i) { + pScene->mMeshes[i] = avList[i].first; + } + + // now we need to update all nodes + this->UpdateNode(pScene->mRootNode,avList); + ASSIMP_LOG_INFO("SplitLargeMeshesProcess_Triangle finished. Meshes have been split"); } // ------------------------------------------------------------------------------------------------ @@ -105,8 +99,12 @@ void SplitLargeMeshesProcess_Triangle::SetupProperties( const Importer* pImp) { // ------------------------------------------------------------------------------------------------ // Update a node after some meshes have been split -void SplitLargeMeshesProcess_Triangle::UpdateNode(aiNode* pcNode, - const std::vector >& avList) { +void SplitLargeMeshesProcess_Triangle::UpdateNode(aiNode* pcNode, const std::vector >& avList) { + if (pcNode == nullptr) { + ASSIMP_LOG_WARN("UpdateNode skipped, nullptr detected."); + return; + } + // for every index in out list build a new entry std::vector aiEntries; aiEntries.reserve(pcNode->mNumMeshes + 1); @@ -329,9 +327,6 @@ SplitLargeMeshesProcess_Vertex::SplitLargeMeshesProcess_Vertex() { LIMIT = AI_SLM_DEFAULT_MAX_VERTICES; } -// ------------------------------------------------------------------------------------------------ -SplitLargeMeshesProcess_Vertex::~SplitLargeMeshesProcess_Vertex() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool SplitLargeMeshesProcess_Vertex::IsActive( unsigned int pFlags) const { diff --git a/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.h b/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.h index e5a8d4c1b..25bf300d5 100644 --- a/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.h +++ b/Engine/lib/assimp/code/PostProcessing/SplitLargeMeshes.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -83,16 +83,15 @@ class SplitLargeMeshesProcess_Vertex; * Applied BEFORE the JoinVertices-Step occurs. * Returns NON-UNIQUE vertices, splits by triangle number. */ -class ASSIMP_API SplitLargeMeshesProcess_Triangle : public BaseProcess -{ +class ASSIMP_API SplitLargeMeshesProcess_Triangle : public BaseProcess { friend class SplitLargeMeshesProcess_Vertex; public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SplitLargeMeshesProcess_Triangle(); - ~SplitLargeMeshesProcess_Triangle(); + ~SplitLargeMeshesProcess_Triangle() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. * @param pFlags The processing flags the importer was called with. A @@ -100,16 +99,14 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; - + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; //! Set the split limit - needed for unit testing inline void SetLimit(unsigned int l) @@ -119,14 +116,12 @@ public: inline unsigned int GetLimit() const {return LIMIT;} -public: - // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- //! Apply the algorithm to a given mesh @@ -144,36 +139,31 @@ public: unsigned int LIMIT; }; - // --------------------------------------------------------------------------- /** Post-processing filter to split large meshes into sub-meshes * * Applied AFTER the JoinVertices-Step occurs. * Returns UNIQUE vertices, splits by vertex number. */ -class ASSIMP_API SplitLargeMeshesProcess_Vertex : public BaseProcess -{ +class ASSIMP_API SplitLargeMeshesProcess_Vertex : public BaseProcess { public: - SplitLargeMeshesProcess_Vertex(); - ~SplitLargeMeshesProcess_Vertex(); + ~SplitLargeMeshesProcess_Vertex() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. * @param pFlags The processing flags the importer was called with. A bitwise * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; //! Set the split limit - needed for unit testing inline void SetLimit(unsigned int l) @@ -183,14 +173,12 @@ public: inline unsigned int GetLimit() const {return LIMIT;} -public: - // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- //! Apply the algorithm to a given mesh diff --git a/Engine/lib/assimp/code/PostProcessing/TextureTransform.cpp b/Engine/lib/assimp/code/PostProcessing/TextureTransform.cpp index efbf4d2c6..228e97e42 100644 --- a/Engine/lib/assimp/code/PostProcessing/TextureTransform.cpp +++ b/Engine/lib/assimp/code/PostProcessing/TextureTransform.cpp @@ -2,8 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -42,8 +41,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file A helper class that processes texture transformations */ - - #include #include #include @@ -56,33 +53,24 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -TextureTransformStep::TextureTransformStep() : - configFlags() -{ +TextureTransformStep::TextureTransformStep() : configFlags() { // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -TextureTransformStep::~TextureTransformStep() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool TextureTransformStep::IsActive( unsigned int pFlags) const -{ +bool TextureTransformStep::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_TransformUVCoords) != 0; } // ------------------------------------------------------------------------------------------------ // Setup properties -void TextureTransformStep::SetupProperties(const Importer* pImp) -{ +void TextureTransformStep::SetupProperties(const Importer* pImp) { configFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_TUV_EVALUATE,AI_UVTRAFO_ALL); } // ------------------------------------------------------------------------------------------------ -void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) -{ +void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) { /* This function tries to simplify the input UV transformation. * That's very important as it allows us to reduce the number * of output UV channels. The order in which the transformations @@ -90,7 +78,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) */ int rounded; - char szTemp[512]; + char szTemp[512] = {}; /* Optimize the rotation angle. That's slightly difficult as * we have an inprecise floating-point number (when comparing @@ -98,12 +86,10 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) * an epsilon of 5 degrees). If there is a rotation value, we can't * perform any further optimizations. */ - if (info.mRotation) - { + if (info.mRotation) { float out = info.mRotation; rounded = static_cast((info.mRotation / static_cast(AI_MATH_TWO_PI))); - if (rounded) - { + if (rounded) { out -= rounded * static_cast(AI_MATH_PI); ASSIMP_LOG_INFO("Texture coordinate rotation ", info.mRotation, " can be simplified to ", out); } @@ -187,8 +173,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) } // ------------------------------------------------------------------------------------------------ -void UpdateUVIndex(const std::list& l, unsigned int n) -{ +void UpdateUVIndex(const std::list& l, unsigned int n) { // Don't set if == 0 && wasn't set before for (std::list::const_iterator it = l.begin();it != l.end(); ++it) { const TTUpdateInfo& info = *it; @@ -203,8 +188,7 @@ void UpdateUVIndex(const std::list& l, unsigned int n) } // ------------------------------------------------------------------------------------------------ -inline const char* MappingModeToChar(aiTextureMapMode map) -{ +inline static const char* MappingModeToChar(aiTextureMapMode map) { if (aiTextureMapMode_Wrap == map) return "-w"; @@ -215,8 +199,7 @@ inline const char* MappingModeToChar(aiTextureMapMode map) } // ------------------------------------------------------------------------------------------------ -void TextureTransformStep::Execute( aiScene* pScene) -{ +void TextureTransformStep::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("TransformUVCoordsProcess begin"); @@ -407,8 +390,8 @@ void TextureTransformStep::Execute( aiScene* pScene) cnt = 0; for (it = trafo.begin();it != trafo.end(); ++it,++cnt) { if ((*it).lockedPos != AI_TT_UV_IDX_LOCK_NONE && (*it).lockedPos != cnt) { - it2 = trafo.begin();unsigned int t = 0; - while (t != (*it).lockedPos) + it2 = trafo.begin(); + while ((*it2).lockedPos != (*it).lockedPos) ++it2; if ((*it2).lockedPos != AI_TT_UV_IDX_LOCK_NONE) { @@ -508,8 +491,9 @@ void TextureTransformStep::Execute( aiScene* pScene) ai_assert(nullptr != src); // Copy the data to the destination array - if (dest != src) + if (dest != src) { ::memcpy(dest,src,sizeof(aiVector3D)*mesh->mNumVertices); + } end = dest + mesh->mNumVertices; diff --git a/Engine/lib/assimp/code/PostProcessing/TextureTransform.h b/Engine/lib/assimp/code/PostProcessing/TextureTransform.h index c1cccf8ef..7c0addf09 100644 --- a/Engine/lib/assimp/code/PostProcessing/TextureTransform.h +++ b/Engine/lib/assimp/code/PostProcessing/TextureTransform.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -193,28 +193,23 @@ struct STransformVecInfo : public aiUVTransform { /** Helper step to compute final UV coordinate sets if there are scalings * or rotations in the original data read from the file. */ -class TextureTransformStep : public BaseProcess -{ +class TextureTransformStep : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. TextureTransformStep(); - ~TextureTransformStep(); - -public: + ~TextureTransformStep() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; protected: - - // ------------------------------------------------------------------- /** Preprocess a specific UV transformation setup * @@ -223,10 +218,9 @@ protected: void PreProcessUVTransform(STransformVecInfo& info); private: - unsigned int configFlags; }; - -} + +} // namespace Assimp #endif //! AI_TEXTURE_TRANSFORM_H_INCLUDED diff --git a/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.cpp b/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.cpp index 52e760361..c0ffffd6b 100644 --- a/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.cpp +++ b/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -156,26 +156,15 @@ namespace { } - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -TriangulateProcess::TriangulateProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -TriangulateProcess::~TriangulateProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool TriangulateProcess::IsActive( unsigned int pFlags) const -{ +bool TriangulateProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_Triangulate) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void TriangulateProcess::Execute( aiScene* pScene) -{ +void TriangulateProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("TriangulateProcess begin"); bool bHas = false; @@ -196,21 +185,20 @@ void TriangulateProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Triangulates the given mesh. -bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) -{ +bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) { // Now we have aiMesh::mPrimitiveTypes, so this is only here for test cases if (!pMesh->mPrimitiveTypes) { bool bNeed = false; for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { const aiFace& face = pMesh->mFaces[a]; - if( face.mNumIndices != 3) { bNeed = true; } } - if (!bNeed) + if (!bNeed) { return false; + } } else if (!(pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) { return false; @@ -225,18 +213,19 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) get_normals = false; } if( face.mNumIndices <= 3) { - numOut++; - - } - else { + ++numOut; + } else { numOut += face.mNumIndices-2; max_out = std::max(max_out,face.mNumIndices); } } // Just another check whether aiMesh::mPrimitiveTypes is correct - ai_assert(numOut != pMesh->mNumFaces); - + if (numOut == pMesh->mNumFaces) { + ASSIMP_LOG_ERROR( "Invalidation detected in the number of indices: does not fit to the primitive type." ); + return false; + } + aiVector3D *nor_out = nullptr; // if we don't have normals yet, but expect them to be a cheap side @@ -464,7 +453,22 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) *pnt2 = &temp_verts[next]; // Must be a convex point. Assuming ccw winding, it must be on the right of the line between p-1 and p+1. - if (OnLeftSideOfLine2D(*pnt0,*pnt2,*pnt1)) { + if (OnLeftSideOfLine2D(*pnt0,*pnt2,*pnt1) == 1) { + continue; + } + + // Skip when three point is in a line + aiVector2D left = *pnt0 - *pnt1; + aiVector2D right = *pnt2 - *pnt1; + + left.Normalize(); + right.Normalize(); + auto mul = left * right; + + // if the angle is 0 or 180 + if (std::abs(mul - 1.f) < ai_epsilon || std::abs(mul + 1.f) < ai_epsilon) { + // skip this ear + ASSIMP_LOG_WARN("Skip a ear, due to its angle is near 0 or 180."); continue; } @@ -505,22 +509,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) #endif num = 0; break; - - /*curOut -= (max-num); // undo all previous work - for (tmp = 0; tmp < max-2; ++tmp) { - aiFace& nface = *curOut++; - - nface.mNumIndices = 3; - if (!nface.mIndices) - nface.mIndices = new unsigned int[3]; - - nface.mIndices[0] = 0; - nface.mIndices[1] = tmp+1; - nface.mIndices[2] = tmp+2; - - } - num = 0; - break;*/ } aiFace& nface = *curOut++; @@ -574,23 +562,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) for(aiFace* f = last_face; f != curOut; ) { unsigned int* i = f->mIndices; - // drop dumb 0-area triangles - deactivated for now: - //FindDegenerates post processing step can do the same thing - //if (std::fabs(GetArea2D(temp_verts[i[0]],temp_verts[i[1]],temp_verts[i[2]])) < 1e-5f) { - // ASSIMP_LOG_VERBOSE_DEBUG("Dropping triangle with area 0"); - // --curOut; - - // delete[] f->mIndices; - // f->mIndices = nullptr; - - // for(aiFace* ff = f; ff != curOut; ++ff) { - // ff->mNumIndices = (ff+1)->mNumIndices; - // ff->mIndices = (ff+1)->mIndices; - // (ff+1)->mIndices = nullptr; - // } - // continue; - //} - i[0] = idx[i[0]]; i[1] = idx[i[1]]; i[2] = idx[i[2]]; diff --git a/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.h b/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.h index ed5f4a587..e17a10e33 100644 --- a/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.h +++ b/Engine/lib/assimp/code/PostProcessing/TriangulateProcess.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -61,8 +61,10 @@ namespace Assimp { */ class ASSIMP_API TriangulateProcess : public BaseProcess { public: - TriangulateProcess(); - ~TriangulateProcess(); + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + TriangulateProcess() = default; + ~TriangulateProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -70,14 +72,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Triangulates the given mesh. diff --git a/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.cpp b/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.cpp index 54889f34b..1e225e212 100644 --- a/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.cpp +++ b/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.cpp @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team - - +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -60,12 +58,7 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -ValidateDSProcess::ValidateDSProcess() : - mScene() {} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ValidateDSProcess::~ValidateDSProcess() = default; +ValidateDSProcess::ValidateDSProcess() : mScene(nullptr) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. @@ -80,7 +73,7 @@ AI_WONT_RETURN void ValidateDSProcess::ReportError(const char *msg, ...) { va_start(args, msg); char szBuffer[3000]; - const int iLen = vsprintf(szBuffer, msg, args); + const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args); ai_assert(iLen > 0); va_end(args); @@ -95,7 +88,7 @@ void ValidateDSProcess::ReportWarning(const char *msg, ...) { va_start(args, msg); char szBuffer[3000]; - const int iLen = vsprintf(szBuffer, msg, args); + const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args); ai_assert(iLen > 0); va_end(args); @@ -115,18 +108,21 @@ inline int HasNameMatch(const aiString &in, aiNode *node) { template inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) { // validate all entries - if (size) { - if (!parray) { - ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", - firstName, secondName, size); - } - for (unsigned int i = 0; i < size; ++i) { - if (!parray[i]) { - ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", - firstName, i, secondName, size); - } - Validate(parray[i]); + if (size == 0) { + return; + } + + if (!parray) { + ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", + firstName, secondName, size); + } + + for (unsigned int i = 0; i < size; ++i) { + if (!parray[i]) { + ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)", + firstName, i, secondName, size); } + Validate(parray[i]); } } @@ -135,25 +131,27 @@ template inline void ValidateDSProcess::DoValidationEx(T **parray, unsigned int size, const char *firstName, const char *secondName) { // validate all entries - if (size) { - if (!parray) { - ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", - firstName, secondName, size); + if (size == 0) { + return; + } + + if (!parray) { + ReportError("aiScene::%s is nullptr (aiScene::%s is %i)", + firstName, secondName, size); + } + for (unsigned int i = 0; i < size; ++i) { + if (!parray[i]) { + ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", + firstName, i, secondName, size); } - for (unsigned int i = 0; i < size; ++i) { - if (!parray[i]) { - ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)", - firstName, i, secondName, size); - } - Validate(parray[i]); + Validate(parray[i]); - // check whether there are duplicate names - for (unsigned int a = i + 1; a < size; ++a) { - if (parray[i]->mName == parray[a]->mName) { - ReportError("aiScene::%s[%u] has the same name as " - "aiScene::%s[%u]", - firstName, i, secondName, a); - } + // check whether there are duplicate names + for (unsigned int a = i + 1; a < size; ++a) { + if (parray[i]->mName == parray[a]->mName) { + ReportError("aiScene::%s[%u] has the same name as " + "aiScene::%s[%u]", + firstName, i, secondName, a); } } } @@ -234,12 +232,6 @@ void ValidateDSProcess::Execute(aiScene *pScene) { if (pScene->mNumMaterials) { DoValidation(pScene->mMaterials, pScene->mNumMaterials, "mMaterials", "mNumMaterials"); } -#if 0 - // NOTE: ScenePreprocessor generates a default material if none is there - else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) { - ReportError("aiScene::mNumMaterials is 0. At least one material must be there"); - } -#endif else if (pScene->mMaterials) { ReportError("aiScene::mMaterials is non-null although there are no materials"); } @@ -272,8 +264,7 @@ void ValidateDSProcess::Validate(const aiCamera *pCamera) { if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear) ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear"); - // FIX: there are many 3ds files with invalid FOVs. No reason to - // reject them at all ... a warning is appropriate. + // There are many 3ds files with invalid FOVs. No reason to reject them at all ... a warning is appropriate. if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI) ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV", pCamera->mHorizontalFOV); } @@ -295,7 +286,6 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh) { switch (face.mNumIndices) { case 0: ReportError("aiMesh::mFaces[%i].mNumIndices is 0", i); - break; case 1: if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) { ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes " @@ -367,15 +357,6 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh) { if (face.mIndices[a] >= pMesh->mNumVertices) { ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range", i, a); } - // the MSB flag is temporarily used by the extra verbose - // mode to tell us that the JoinVerticesProcess might have - // been executed already. - /*if ( !(this->mScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT ) && !(this->mScene->mFlags & AI_SCENE_FLAGS_ALLOW_SHARED) && - abRefList[face.mIndices[a]]) - { - ReportError("aiMesh::mVertices[%i] is referenced twice - second " - "time by aiMesh::mFaces[%i]::mIndices[%i]",face.mIndices[a],i,a); - }*/ abRefList[face.mIndices[a]] = true; } } @@ -390,20 +371,7 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh) { ReportWarning("There are unreferenced vertices"); } - // texture channel 2 may not be set if channel 1 is zero ... - { - unsigned int i = 0; - for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if (!pMesh->HasTextureCoords(i)) break; - } - for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) - if (pMesh->HasTextureCoords(i)) { - ReportError("Texture coordinate channel %i exists " - "although the previous channel was nullptr.", - i); - } - } - // the same for the vertex colors + // vertex color channel 2 may not be set if channel 1 is zero ... { unsigned int i = 0; for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { @@ -471,7 +439,7 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float this->Validate(&pBone->mName); if (!pBone->mNumWeights) { - //ReportError("aiBone::mNumWeights is zero"); + ReportWarning("aiBone::mNumWeights is zero"); } // check whether all vertices affected by this bone are valid @@ -479,7 +447,7 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) { ReportError("aiBone::mWeights[%i].mVertexId is out of range", i); } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) { - ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value", i); + ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value %i. Value must be greater than zero and less than 1.", i, pBone->mWeights[i].mWeight); } afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight; } @@ -916,16 +884,24 @@ void ValidateDSProcess::Validate(const aiNode *pNode) { nodeName, pNode->mNumChildren); } for (unsigned int i = 0; i < pNode->mNumChildren; ++i) { - Validate(pNode->mChildren[i]); + const aiNode *pChild = pNode->mChildren[i]; + Validate(pChild); + if (pChild->mParent != pNode) { + const char *parentName = (pChild->mParent != nullptr) ? pChild->mParent->mName.C_Str() : "null"; + ReportError("aiNode \"%s\" child %i \"%s\" parent is someone else: \"%s\"", pNode->mName.C_Str(), i, pChild->mName.C_Str(), parentName); + } } + } else if (pNode->mChildren) { + ReportError("aiNode::mChildren is not nullptr for empty node %s (aiNode::mNumChildren is %i)", + nodeName, pNode->mNumChildren); } } // ------------------------------------------------------------------------------------------------ void ValidateDSProcess::Validate(const aiString *pString) { - if (pString->length > MAXLEN) { + if (pString->length > AI_MAXLEN) { ReportError("aiString::length is too large (%u, maximum is %lu)", - pString->length, MAXLEN); + pString->length, AI_MAXLEN); } const char *sz = pString->data; while (true) { @@ -934,7 +910,7 @@ void ValidateDSProcess::Validate(const aiString *pString) { ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); } break; - } else if (sz >= &pString->data[MAXLEN]) { + } else if (sz >= &pString->data[AI_MAXLEN]) { ReportError("aiString::data is invalid. There is no terminal character"); } ++sz; diff --git a/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.h b/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.h index 077a47b70..8bc13e60d 100644 --- a/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.h +++ b/Engine/lib/assimp/code/PostProcessing/ValidateDataStructure.h @@ -2,7 +2,7 @@ Open Asset Import Library (assimp) ---------------------------------------------------------------------- -Copyright (c) 2006-2022, assimp team +Copyright (c) 2006-2024, assimp team All rights reserved. @@ -69,22 +69,20 @@ namespace Assimp { /** Validates the whole ASSIMP scene data structure for correctness. * ImportErrorException is thrown of the scene is corrupt.*/ // -------------------------------------------------------------------------------------- -class ValidateDSProcess : public BaseProcess -{ +class ValidateDSProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ValidateDSProcess(); - ~ValidateDSProcess(); - -public: - // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + ~ValidateDSProcess() override = default; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + bool IsActive( unsigned int pFlags) const override; + + // ------------------------------------------------------------------- + void Execute( aiScene* pScene) override; protected: - // ------------------------------------------------------------------- /** Report a validation error. This will throw an exception, * control won't return. diff --git a/Engine/lib/assimp/code/res/assimp.rc b/Engine/lib/assimp/code/res/assimp.rc index fd10935f6..6c9356276 100644 --- a/Engine/lib/assimp/code/res/assimp.rc +++ b/Engine/lib/assimp/code/res/assimp.rc @@ -1,4 +1,4 @@ -#include "revision.h" +#include #ifdef __GNUC__ #include "winresrc.h" #else diff --git a/Engine/lib/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.cpp b/Engine/lib/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.cpp index 2ae70fa2e..c1935822d 100644 --- a/Engine/lib/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.cpp +++ b/Engine/lib/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.cpp @@ -92,6 +92,7 @@ namespace o3dgc // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - Static functions - - - - - - - - - - - - - - - - - - - - - - - - - - - + AI_WONT_RETURN static void AC_Error(const char * msg) AI_WONT_RETURN_SUFFIX; static void AC_Error(const char * msg) { fprintf(stderr, "\n\n -> Arithmetic coding error: "); diff --git a/Engine/lib/assimp/contrib/clipper/License.txt b/Engine/lib/assimp/contrib/clipper/License.txt index 8e2278cef..3e3af47ba 100644 --- a/Engine/lib/assimp/contrib/clipper/License.txt +++ b/Engine/lib/assimp/contrib/clipper/License.txt @@ -1,7 +1,3 @@ -The Clipper code library, the "Software" (that includes Delphi, C++ & C# -source code, accompanying samples and documentation), has been released -under the following license, terms and conditions: - Boost Software License - Version 1.0 - August 17th, 2003 http://www.boost.org/LICENSE_1_0.txt @@ -25,5 +21,4 @@ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Engine/lib/assimp/contrib/clipper/clipper.cpp b/Engine/lib/assimp/contrib/clipper/clipper.cpp index 2d02aff67..f7a74ed3a 100644 --- a/Engine/lib/assimp/contrib/clipper/clipper.cpp +++ b/Engine/lib/assimp/contrib/clipper/clipper.cpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 4.8.8 * -* Date : 30 August 2012 * +* Version : 6.4.2 * +* Date : 27 February 2017 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2012 * +* Copyright : Angus Johnson 2010-2017 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -43,28 +43,202 @@ #include #include #include -#include #include #include #include +#include namespace ClipperLib { -static long64 const loRange = 0x3FFFFFFF; -static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL; static double const pi = 3.141592653589793238; +static double const two_pi = pi *2; +static double const def_arc_tolerance = 0.25; + enum Direction { dRightToLeft, dLeftToRight }; +static int const Unassigned = -1; //edge not currently 'owning' a solution +static int const Skip = -2; //edge that would otherwise close a path + #define HORIZONTAL (-1.0E+40) #define TOLERANCE (1.0e-20) #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) -#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b)) -inline long64 Abs(long64 val) +struct TEdge { + IntPoint Bot; + IntPoint Curr; //current (updated for every new scanbeam) + IntPoint Top; + double Dx; + PolyType PolyTyp; + EdgeSide Side; //side only refers to current side of solution poly + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinimum { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; +}; + +struct OutPt; + +//OutRec: contains a path in the clipping solution. Edges in the AEL will +//carry a pointer to an OutRec when they are part of the clipping solution. +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +struct LocMinSorter +{ + inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) + { + return locMin2.Y < locMin1.Y; + } +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) { return val < 0 ? -val : val; } + //------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + int result = (int)AllNodes.size(); + //with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Childs[0] != AllNodes[0]) result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Parent(0), Index(0), m_IsOpen(false) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return (int)Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = (unsigned)Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 //------------------------------------------------------------------------------ // Int128 class (enables safe math on signed 64bit integers) @@ -77,27 +251,25 @@ inline long64 Abs(long64 val) class Int128 { public: + ulong64 lo; + long64 hi; Int128(long64 _lo = 0) { - lo = _lo; - if (lo < 0) hi = -1; else hi = 0; + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; } - Int128(const Int128 &val): hi(val.hi), lo(val.lo){} - Int128 operator = (const Int128 &val) - { - lo = val.lo; - hi = val.hi; - return val; - } + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - long64 operator = (const long64 &val) + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + Int128& operator = (const long64 &val) { - lo = val; - if (lo < 0) hi = -1; else hi = 0; - return val; + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; } bool operator == (const Int128 &val) const @@ -132,7 +304,7 @@ class Int128 { hi += rhs.hi; lo += rhs.lo; - if (ulong64(lo) < ulong64(rhs.lo)) hi++; + if (lo < rhs.lo) hi++; return *this; } @@ -145,25 +317,10 @@ class Int128 Int128& operator -= (const Int128 &rhs) { - Int128 tmp(rhs); - Negate(tmp); - *this += tmp; + *this += -rhs; return *this; } - //Int128 operator -() const - //{ - // Int128 result(*this); - // if (result.lo == 0) { - // if (result.hi != 0) result.hi = -1; - // } - // else { - // result.lo = -result.lo; - // result.hi = ~result.hi; - // } - // return result; - //} - Int128 operator - (const Int128 &rhs) const { Int128 result(*this); @@ -171,546 +328,441 @@ class Int128 return result; } - Int128 operator * (const Int128 &rhs) const + Int128 operator-() const //unary negation { - if ( !(hi == 0 || hi == -1) || !(rhs.hi == 0 || rhs.hi == -1)) - throw "Int128 operator*: overflow error"; - bool negate = (hi < 0) != (rhs.hi < 0); - - Int128 tmp(*this); - if (tmp.hi < 0) Negate(tmp); - ulong64 int1Hi = ulong64(tmp.lo) >> 32; - ulong64 int1Lo = ulong64(tmp.lo & 0xFFFFFFFF); - - tmp = rhs; - if (tmp.hi < 0) Negate(tmp); - ulong64 int2Hi = ulong64(tmp.lo) >> 32; - ulong64 int2Lo = ulong64(tmp.lo & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (ulong64(tmp.lo) < b) tmp.hi++; - if (negate) Negate(tmp); - return tmp; + if (lo == 0) + return Int128(-hi, 0); + else + return Int128(~hi, ~lo + 1); } - Int128 operator/ (const Int128 &rhs) const - { - if (rhs.lo == 0 && rhs.hi == 0) - throw "Int128 operator/: divide by zero"; - bool negate = (rhs.hi < 0) != (hi < 0); - Int128 result(*this), denom(rhs); - if (result.hi < 0) Negate(result); - if (denom.hi < 0) Negate(denom); - if (denom > result) return Int128(0); //result is only a fraction of 1 - Negate(denom); - - Int128 p(0); - for (int i = 0; i < 128; ++i) - { - p.hi = p.hi << 1; - if (p.lo < 0) p.hi++; - p.lo = long64(p.lo) << 1; - if (result.hi < 0) p.lo++; - result.hi = result.hi << 1; - if (result.lo < 0) result.hi++; - result.lo = long64(result.lo) << 1; - Int128 p2(p); - p += denom; - if (p.hi < 0) p = p2; - else result.lo++; - } - if (negate) Negate(result); - return result; - } - - double AsDouble() const + operator double() const { const double shift64 = 18446744073709551616.0; //2^64 - const double bit64 = 9223372036854775808.0; if (hi < 0) { - Int128 tmp(*this); - Negate(tmp); - if (tmp.lo < 0) - return (double)tmp.lo - bit64 - tmp.hi * shift64; - else - return -(double)tmp.lo - tmp.hi * shift64; + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); } - else if (lo < 0) - return -(double)lo + bit64 + hi * shift64; else - return (double)lo + (double)hi * shift64; + return (double)(lo + hi * shift64); } - //for bug testing ... - //std::string AsString() const - //{ - // std::string result; - // unsigned char r = 0; - // Int128 tmp(0), val(*this); - // if (hi < 0) Negate(val); - // result.resize(50); - // std::string::size_type i = result.size() -1; - // while (val.hi != 0 || val.lo != 0) - // { - // Div10(val, tmp, r); - // result[i--] = char('0' + r); - // val = tmp; - // } - // if (hi < 0) result[i--] = '-'; - // result.erase(0,i+1); - // if (result.size() == 0) result = "0"; - // return result; - //} - -private: - long64 hi; - long64 lo; - - static void Negate(Int128 &val) - { - if (val.lo == 0) { - if (val.hi != 0) val.hi = -val.hi;; - } - else { - val.lo = -val.lo; - val.hi = ~val.hi; - } - } - - //debugging only ... - //void Div10(const Int128 val, Int128& result, unsigned char & remainder) const - //{ - // remainder = 0; - // result = 0; - // for (int i = 63; i >= 0; --i) - // { - // if ((val.hi & ((long64)1 << i)) != 0) - // remainder = char((remainder * 2) + 1); else - // remainder *= char(2); - // if (remainder >= 10) - // { - // result.hi += ((long64)1 << i); - // remainder -= char(10); - // } - // } - // for (int i = 63; i >= 0; --i) - // { - // if ((val.lo & ((long64)1 << i)) != 0) - // remainder = char((remainder * 2) + 1); else - // remainder *= char(2); - // if (remainder >= 10) - // { - // result.lo += ((long64)1 << i); - // remainder -= char(10); - // } - // } - //} }; - -//------------------------------------------------------------------------------ //------------------------------------------------------------------------------ -bool FullRangeNeeded(const Polygon &pts) +Int128 Int128Mul (long64 lhs, long64 rhs) { - bool result = false; - for (Polygon::size_type i = 0; i < pts.size(); ++i) - { - if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange) - throw "Coordinate exceeds range bounds."; - else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange) - result = true; - } - return result; -} + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + //------------------------------------------------------------------------------ - -bool Orientation(const Polygon &poly) +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) { - int highI = (int)poly.size() -1; - if (highI < 2) return false; - - int j = 0, jplus, jminus; - for (int i = 0; i <= highI; ++i) - { - if (poly[i].Y < poly[j].Y) continue; - if ((poly[i].Y > poly[j].Y || poly[i].X < poly[j].X)) j = i; - }; - if (j == highI) jplus = 0; - else jplus = j +1; - if (j == 0) jminus = highI; - else jminus = j -1; - - IntPoint vec1, vec2; - //get cross product of vectors of the edges adjacent to highest point ... - vec1.X = poly[j].X - poly[jminus].X; - vec1.Y = poly[j].Y - poly[jminus].Y; - vec2.X = poly[jplus].X - poly[j].X; - vec2.Y = poly[jplus].Y - poly[j].Y; - - if (Abs(vec1.X) > loRange || Abs(vec1.Y) > loRange || - Abs(vec2.X) > loRange || Abs(vec2.Y) > loRange) - { - if (Abs(vec1.X) > hiRange || Abs(vec1.Y) > hiRange || - Abs(vec2.X) > hiRange || Abs(vec2.Y) > hiRange) - throw "Coordinate exceeds range bounds."; - Int128 cross = Int128(vec1.X) * Int128(vec2.Y) - - Int128(vec2.X) * Int128(vec1.Y); - return cross >= 0; - } - else - return (vec1.X * vec2.Y - vec2.X * vec1.Y) >= 0; + return Area(poly) >= 0; } //------------------------------------------------------------------------------ -inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) +double Area(const Path &poly) { - return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); -} -//------------------------------------------------------------------------------ + int size = (int)poly.size(); + if (size < 3) return 0; -bool Orientation(OutRec *outRec, bool UseFullInt64Range) -{ - if (!outRec->pts) - return 0.0; - - //first make sure bottomPt is correctly assigned ... - OutPt *opBottom = outRec->pts, *op = outRec->pts->next; - while (op != outRec->pts) + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) { - if (op->pt.Y >= opBottom->pt.Y) - { - if (op->pt.Y > opBottom->pt.Y || op->pt.X < opBottom->pt.X) - opBottom = op; - } - op = op->next; + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; } - outRec->bottomPt = opBottom; - opBottom->idx = outRec->idx; - - op = opBottom; - //find vertices either side of bottomPt (skipping duplicate points) .... - OutPt *opPrev = op->prev; - OutPt *opNext = op->next; - while (op != opPrev && PointsEqual(op->pt, opPrev->pt)) - opPrev = opPrev->prev; - while (op != opNext && PointsEqual(op->pt, opNext->pt)) - opNext = opNext->next; - - IntPoint ip1, ip2; - ip1.X = op->pt.X - opPrev->pt.X; - ip1.Y = op->pt.Y - opPrev->pt.Y; - ip2.X = opNext->pt.X - op->pt.X; - ip2.Y = opNext->pt.Y - op->pt.Y; - - if (UseFullInt64Range) - return Int128(ip1.X) * Int128(ip2.Y) - Int128(ip2.X) * Int128(ip1.Y) >= 0; - else - return (ip1.X * ip2.Y - ip2.X * ip1.Y) >= 0; + return -a * 0.5; } //------------------------------------------------------------------------------ -double Area(const Polygon &poly) +double Area(const OutPt *op) { - int highI = (int)poly.size() -1; - if (highI < 2) return 0; - - if (FullRangeNeeded(poly)) { - Int128 a; - a = (Int128(poly[highI].X) * Int128(poly[0].Y)) - - Int128(poly[0].X) * Int128(poly[highI].Y); - for (int i = 0; i < highI; ++i) - a += Int128(poly[i].X) * Int128(poly[i+1].Y) - - Int128(poly[i+1].X) * Int128(poly[i].Y); - return a.AsDouble() / 2; - } - else - { - double a; - a = (double)poly[highI].X * poly[0].Y - (double)poly[0].X * poly[highI].Y; - for (int i = 0; i < highI; ++i) - a += (double)poly[i].X * poly[i+1].Y - (double)poly[i+1].X * poly[i].Y; - return a/2; - } + const OutPt *startOp = op; + if (!op) return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != startOp); + return a * 0.5; } //------------------------------------------------------------------------------ -double Area(const OutRec &outRec, bool UseFullInt64Range) +double Area(const OutRec &outRec) { - if (!outRec.pts) - return 0.0; - - OutPt *op = outRec.pts; - if (UseFullInt64Range) { - Int128 a(0); - do { - a += (Int128(op->prev->pt.X) * Int128(op->pt.Y)) - - Int128(op->pt.X) * Int128(op->prev->pt.Y); - op = op->next; - } while (op != outRec.pts); - return a.AsDouble() / 2; - } - else - { - double a = 0; - do { - a += (op->prev->pt.X * op->pt.Y) - (op->pt.X * op->prev->pt.Y); - op = op->next; - } while (op != outRec.pts); - return a/2; - } + return Area(outRec.Pts); } //------------------------------------------------------------------------------ -bool PointIsVertex(const IntPoint &pt, OutPt *pp) +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { OutPt *pp2 = pp; do { - if (PointsEqual(pp2->pt, pt)) return true; - pp2 = pp2->next; + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; } while (pp2 != pp); return false; } //------------------------------------------------------------------------------ -bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) +//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos +//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf +int PointInPolygon(const IntPoint &pt, const Path &path) { - OutPt *pp2 = pp; - bool result = false; - if (UseFullInt64Range) { - do - { - if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || - ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && - Int128(pt.X - pp2->pt.X) < (Int128(pp2->prev->pt.X - pp2->pt.X) * - Int128(pt.Y - pp2->pt.Y)) / Int128(pp2->prev->pt.Y - pp2->pt.Y)) - result = !result; - pp2 = pp2->next; - } - while (pp2 != pp); - } - else + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for(size_t i = 1; i <= cnt; ++i) { - do + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) { - if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || - ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && - (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) / - (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result; - pp2 = pp2->next; + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; } - while (pp2 != pp); - } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } return result; } //------------------------------------------------------------------------------ -bool SlopesEqual(TEdge &e1, TEdge &e2, bool UseFullInt64Range) +int PointInPolygon (const IntPoint &pt, OutPt *op) { + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt* startOp = op; + for(;;) + { + if (op->Next->Pt.Y == pt.Y) + { + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) +{ + OutPt* op = OutPt1; + do + { + //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res > 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ +#ifndef use_int32 if (UseFullInt64Range) - return Int128(e1.ytop - e1.ybot) * Int128(e2.xtop - e2.xbot) == - Int128(e1.xtop - e1.xbot) * Int128(e2.ytop - e2.ybot); - else return (e1.ytop - e1.ybot)*(e2.xtop - e2.xbot) == - (e1.xtop - e1.xbot)*(e2.ytop - e2.ybot); + return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == + Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); + else +#endif + return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == + (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); } //------------------------------------------------------------------------------ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, bool UseFullInt64Range) { +#ifndef use_int32 if (UseFullInt64Range) - return Int128(pt1.Y-pt2.Y) * Int128(pt2.X-pt3.X) == - Int128(pt1.X-pt2.X) * Int128(pt2.Y-pt3.Y); - else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); } //------------------------------------------------------------------------------ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) { +#ifndef use_int32 if (UseFullInt64Range) - return Int128(pt1.Y-pt2.Y) * Int128(pt3.X-pt4.X) == - Int128(pt1.X-pt2.X) * Int128(pt3.Y-pt4.Y); - else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); } //------------------------------------------------------------------------------ -double GetDx(const IntPoint pt1, const IntPoint pt2) +inline bool IsHorizontal(TEdge &e) +{ + return e.Dx == HORIZONTAL; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) { return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (double)(pt2.Y - pt1.Y); + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); } //--------------------------------------------------------------------------- -void SetDx(TEdge &e) +inline void SetDx(TEdge &e) { - if (e.ybot == e.ytop) e.dx = HORIZONTAL; - else e.dx = (double)(e.xtop - e.xbot) / (double)(e.ytop - e.ybot); + cInt dy = (e.Top.Y - e.Bot.Y); + if (dy == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; } //--------------------------------------------------------------------------- -void SwapSides(TEdge &edge1, TEdge &edge2) +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) { - EdgeSide side = edge1.side; - edge1.side = edge2.side; - edge2.side = side; + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; } //------------------------------------------------------------------------------ -void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) { - int outIdx = edge1.outIdx; - edge1.outIdx = edge2.outIdx; - edge2.outIdx = outIdx; + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; } //------------------------------------------------------------------------------ -inline long64 Round(double val) +inline cInt TopX(TEdge &edge, const cInt currentY) { - return (val < 0) ? - static_cast(val - 0.5) : static_cast(val + 0.5); + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); } //------------------------------------------------------------------------------ -long64 TopX(TEdge &edge, const long64 currentY) +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { - return ( currentY == edge.ytop ) ? - edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot)); -} -//------------------------------------------------------------------------------ +#ifdef use_xyz + ip.Z = 0; +#endif -long64 TopX(const IntPoint pt1, const IntPoint pt2, const long64 currentY) -{ - //preconditions: pt1.Y <> pt2.Y and pt1.Y > pt2.Y - if (currentY >= pt1.Y) return pt1.X; - else if (currentY == pt2.Y) return pt2.X; - else if (pt1.X == pt2.X) return pt1.X; - else - { - double q = (double)(pt1.X-pt2.X)/(double)(pt1.Y-pt2.Y); - return Round(pt1.X + (currentY - pt1.Y) *q); - } -} -//------------------------------------------------------------------------------ - -bool IntersectPoint(TEdge &edge1, TEdge &edge2, - IntPoint &ip, bool UseFullInt64Range) -{ double b1, b2; - if (SlopesEqual(edge1, edge2, UseFullInt64Range)) return false; - else if (NEAR_ZERO(edge1.dx)) + if (Edge1.Dx == Edge2.Dx) { - ip.X = edge1.xbot; - if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; + } + else if (Edge1.Dx == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else { - ip.Y = edge2.ybot; - } else - { - b2 = edge2.ybot - (edge2.xbot/edge2.dx); - ip.Y = Round(ip.X/edge2.dx + b2); + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); } } - else if (NEAR_ZERO(edge2.dx)) + else if (Edge2.Dx == 0) { - ip.X = edge2.xbot; - if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else { - ip.Y = edge1.ybot; - } else - { - b1 = edge1.ybot - (edge1.xbot/edge1.dx); - ip.Y = Round(ip.X/edge1.dx + b1); + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); } - } else + } + else { - b1 = edge1.xbot - edge1.ybot * edge1.dx; - b2 = edge2.xbot - edge2.ybot * edge2.dx; - b2 = (b2-b1)/(edge1.dx - edge2.dx); - ip.Y = Round(b2); - ip.X = Round(edge1.dx * b2 + b1); + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); } - return - //can be *so close* to the top of one edge that the rounded Y equals one ytop ... - (ip.Y == edge1.ytop && ip.Y >= edge2.ytop && edge1.tmpX > edge2.tmpX) || - (ip.Y == edge2.ytop && ip.Y >= edge1.ytop && edge1.tmpX > edge2.tmpX) || - (ip.Y > edge1.ytop && ip.Y > edge2.ytop); + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) + { + ip.Y = Edge1.Curr.Y; + //use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); else + ip.X = TopX(Edge1, ip.Y); + } } //------------------------------------------------------------------------------ -void ReversePolyPtLinks(OutPt &pp) +void ReversePolyPtLinks(OutPt *pp) { + if (!pp) return; OutPt *pp1, *pp2; - pp1 = &pp; + pp1 = pp; do { - pp2 = pp1->next; - pp1->next = pp1->prev; - pp1->prev = pp2; + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; pp1 = pp2; - } while( pp1 != &pp ); + } while( pp1 != pp ); } //------------------------------------------------------------------------------ void DisposeOutPts(OutPt*& pp) { if (pp == 0) return; - pp->prev->next = 0; + pp->Prev->Next = 0; while( pp ) { OutPt *tmpPp = pp; - pp = pp->next; - delete tmpPp ; + pp = pp->Next; + delete tmpPp; } } //------------------------------------------------------------------------------ -void InitEdge(TEdge *e, TEdge *eNext, - TEdge *ePrev, const IntPoint &pt, PolyType polyType) +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) { - std::memset( e, 0, sizeof( TEdge )); + *e = {}; + //std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ - e->next = eNext; - e->prev = ePrev; - e->xcurr = pt.X; - e->ycurr = pt.Y; - if (e->ycurr >= e->next->ycurr) +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) { - e->xbot = e->xcurr; - e->ybot = e->ycurr; - e->xtop = e->next->xcurr; - e->ytop = e->next->ycurr; - e->windDelta = 1; + e.Bot = e.Curr; + e.Top = e.Next->Curr; } else { - e->xtop = e->xcurr; - e->ytop = e->ycurr; - e->xbot = e->next->xcurr; - e->ybot = e->next->ycurr; - e->windDelta = -1; + e.Top = e.Curr; + e.Bot = e.Next->Curr; } - SetDx(*e); - e->polyType = polyType; - e->outIdx = -1; + SetDx(e); + e.PolyTyp = Pt; } //------------------------------------------------------------------------------ -inline void SwapX(TEdge &e) +TEdge* RemoveEdge(TEdge* e) { - //swap horizontal edges' top and bottom x's so they follow the natural + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = 0; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - e.xcurr = e.xtop; - e.xtop = e.xbot; - e.xbot = e.xcurr; + std::swap(e.Top.X, e.Bot.X); +#ifdef use_xyz + std::swap(e.Top.Z, e.Bot.Z); +#endif } //------------------------------------------------------------------------------ @@ -725,8 +777,8 @@ void SwapPoints(IntPoint &pt1, IntPoint &pt2) bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { - //precondition: segments are colinear. - if ( pt1a.Y == pt1b.Y || Abs((pt1a.X - pt1b.X)/(pt1a.Y - pt1b.Y)) > 1 ) + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) { if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); @@ -746,108 +798,83 @@ bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) { - OutPt *p = btmPt1->prev; - while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev; - double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt)); - p = btmPt1->next; - while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next; - double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt)); + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - p = btmPt2->prev; - while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev; - double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt)); - p = btmPt2->next; - while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next; - double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt)); - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + + if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && + std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) + return Area(btmPt1) > 0; //if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); } //------------------------------------------------------------------------------ OutPt* GetBottomPt(OutPt *pp) { OutPt* dups = 0; - OutPt* p = pp->next; + OutPt* p = pp->Next; while (p != pp) { - if (p->pt.Y > pp->pt.Y) + if (p->Pt.Y > pp->Pt.Y) { pp = p; dups = 0; } - else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X) + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) { - if (p->pt.X < pp->pt.X) + if (p->Pt.X < pp->Pt.X) { dups = 0; pp = p; } else { - if (p->next != pp && p->prev != pp) dups = p; + if (p->Next != pp && p->Prev != pp) dups = p; } } - p = p->next; + p = p->Next; } if (dups) { - //there appears to be at least 2 vertices at bottomPt so ... + //there appears to be at least 2 vertices at BottomPt so ... while (dups != p) { if (!FirstIsBottomPt(p, dups)) pp = dups; - dups = dups->next; - while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; } } return pp; } //------------------------------------------------------------------------------ -bool FindSegment(OutPt* &pp, IntPoint &pt1, IntPoint &pt2) -{ - //outPt1 & outPt2 => the overlap segment (if the function returns true) - if (!pp) return false; - OutPt* pp2 = pp; - IntPoint pt1a = pt1, pt2a = pt2; - do - { - if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, true) && - SlopesEqual(pt1a, pt2a, pp->pt, true) && - GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) - return true; - pp = pp->next; - } - while (pp != pp2); - return false; -} -//------------------------------------------------------------------------------ - -bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1, +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3) { - if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; - else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); - else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); } //------------------------------------------------------------------------------ -OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { - if (p1 == p2) throw "JoinError"; - OutPt* result = new OutPt; - result->pt = pt; - if (p2 == p1->next) - { - p1->next = result; - p2->prev = result; - result->next = p2; - result->prev = p1; - } else - { - p2->next = result; - p1->prev = result; - result->next = p1; - result->prev = p2; - } - return result; + if (seg1a > seg1b) std::swap(seg1a, seg1b); + if (seg2a > seg2b) std::swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); } //------------------------------------------------------------------------------ @@ -856,9 +883,8 @@ OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) ClipperBase::ClipperBase() //constructor { - m_MinimaList = 0; - m_CurrentLM = 0; - m_UseFullRange = true; + m_CurrentLM = m_MinimaList.begin(); //begin() == end() here + m_UseFullRange = false; } //------------------------------------------------------------------------------ @@ -868,177 +894,339 @@ ClipperBase::~ClipperBase() //destructor } //------------------------------------------------------------------------------ -bool ClipperBase::AddPolygon( const Polygon &pg, PolyType polyType) +void RangeTest(const IntPoint& Pt, bool& useFullRange) { - int len = (int)pg.size(); - if (len < 3) return false; - Polygon p(len); - p[0] = pg[0]; - int j = 0; - - long64 maxVal; - if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; - - for (int i = 0; i < len; ++i) + if (useFullRange) { - if (Abs(pg[i].X) > maxVal || Abs(pg[i].Y) > maxVal) + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw clipperException("Coordinate outside allowed range"); + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) +{ + TEdge *Result = E; + TEdge *Horz = 0; + + if (E->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + if (NextIsForward) { - if (Abs(pg[i].X) > hiRange || Abs(pg[i].Y) > hiRange) - throw "Coordinate exceeds range bounds"; - maxVal = hiRange; - m_UseFullRange = true; + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } + else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; } - if (i == 0 || PointsEqual(p[j], pg[i])) continue; - else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) + if (E == Result) { - if (PointsEqual(p[j-1], pg[i])) j--; - } else j++; - p[j] = pg[i]; - } - if (j < 2) return false; - - len = j+1; - while (len > 2) - { - //nb: test for point equality before testing slopes ... - if (PointsEqual(p[j], p[0])) j--; - else if (PointsEqual(p[0], p[1]) || - SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) - p[0] = p[j--]; - else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--; - else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) - { - for (int i = 2; i <= j; ++i) p[i-1] = p[i]; - j--; + if (NextIsForward) Result = E->Next; + else Result = E->Prev; } - else break; - len--; + else + { + //there are more edges in the bound beyond result starting with E + if (NextIsForward) + E = Result->Next; + else + E = Result->Prev; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); + m_MinimaList.push_back(locMin); + } + return Result; } - if (len < 3) return false; + + TEdge *EStart; + + if (IsHorizontal(*E)) + { + //We need to be careful with open paths because this may not be a + //true local minima (ie E may be following a skip edge). + //Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } + else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + + EStart = E; + if (NextIsForward) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X || + Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) +{ +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; //create a new edge array ... - TEdge *edges = new TEdge [len]; - m_edges.push_back(edges); + TEdge *edges = new TEdge [highI +1]; - //convert vertices to a double-linked-list of edges and initialize ... - edges[0].xcurr = p[0].X; - edges[0].ycurr = p[0].Y; - InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType); - for (int i = len-2; i > 0; --i) - InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType); - InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType); + bool IsFlat = true; + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + throw; //range test fails + } + TEdge *eStart = &edges[0]; - //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates - //increase downward so the 'highest' edge will have the smallest ytop) ... - TEdge *e = &edges[0]; - TEdge *eHighest = e; + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) + { + //nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) + { + if (E == E->Next) break; + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + + if (!Closed) + { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; do { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - if (e->ytop < eHighest->ytop) eHighest = e; - e = e->next; + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; } - while ( e != &edges[0]); + while (E != eStart); - //make sure eHighest is positioned so the following loop works safely ... - if (eHighest->windDelta > 0) eHighest = eHighest->next; - if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next; + //4. Finally, add edge bounds to LocalMinima list ... - //finally insert each local minima ... - e = eHighest; - do { - e = AddBoundsToLML(e); + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + locMin.RightBound->Side = esRight; + locMin.RightBound->WindDelta = 0; + for (;;) + { + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + if (E->Next->OutIdx == Skip) break; + E->NextInLML = E->Next; + E = E->Next; + } + m_MinimaList.push_back(locMin); + m_edges.push_back(edges); + return true; + } + + m_edges.push_back(edges); + bool leftBoundIsForward; + TEdge* EMin = 0; + + //workaround to avoid an endless loop in the while loop below when + //open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) E = E->Next; + + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) + { + locMin.LeftBound = E->Prev; + locMin.RightBound = E; + leftBoundIsForward = false; //Q.nextInLML = Q.prev + } else + { + locMin.LeftBound = E; + locMin.RightBound = E->Prev; + leftBoundIsForward = true; //Q.nextInLML = Q.next + } + + if (!Closed) locMin.LeftBound->WindDelta = 0; + else if (locMin.LeftBound->Next == locMin.RightBound) + locMin.LeftBound->WindDelta = -1; + else locMin.LeftBound->WindDelta = 1; + locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); + + TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound->OutIdx == Skip) + locMin.LeftBound = 0; + else if (locMin.RightBound->OutIdx == Skip) + locMin.RightBound = 0; + m_MinimaList.push_back(locMin); + if (!leftBoundIsForward) E = E2; } - while( e != eHighest ); return true; } //------------------------------------------------------------------------------ -void ClipperBase::InsertLocalMinima(LocalMinima *newLm) -{ - if( ! m_MinimaList ) - { - m_MinimaList = newLm; - } - else if( newLm->Y >= m_MinimaList->Y ) - { - newLm->next = m_MinimaList; - m_MinimaList = newLm; - } else - { - LocalMinima* tmpLm = m_MinimaList; - while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) ) - tmpLm = tmpLm->next; - newLm->next = tmpLm->next; - tmpLm->next = newLm; - } -} -//------------------------------------------------------------------------------ - -TEdge* ClipperBase::AddBoundsToLML(TEdge *e) -{ - //Starting at the top of one bound we progress to the bottom where there's - //a local minima. We then go to the top of the next bound. These two bounds - //form the left and right (or right and left) bounds of the local minima. - e->nextInLML = 0; - e = e->next; - for (;;) - { - if (NEAR_EQUAL(e->dx, HORIZONTAL)) - { - //nb: proceed through horizontals when approaching from their right, - // but break on horizontal minima if approaching from their left. - // This ensures 'local minima' are always on the left of horizontals. - if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break; - if (e->xtop != e->prev->xbot) SwapX(*e); - e->nextInLML = e->prev; - } - else if (e->ycurr == e->prev->ycurr) break; - else e->nextInLML = e->prev; - e = e->next; - } - - //e and e.prev are now at a local minima ... - LocalMinima* newLm = new LocalMinima; - newLm->next = 0; - newLm->Y = e->prev->ybot; - - if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound - { - if (e->xbot != e->prev->xbot) SwapX(*e); - newLm->leftBound = e->prev; - newLm->rightBound = e; - } else if (e->dx < e->prev->dx) - { - newLm->leftBound = e->prev; - newLm->rightBound = e; - } else - { - newLm->leftBound = e; - newLm->rightBound = e->prev; - } - newLm->leftBound->side = esLeft; - newLm->rightBound->side = esRight; - InsertLocalMinima( newLm ); - - for (;;) - { - if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break; - e->nextInLML = e->next; - e = e->next; - if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e); - } - return e->next; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { bool result = false; - for (Polygons::size_type i = 0; i < ppg.size(); ++i) - if (AddPolygon(ppg[i], polyType)) result = true; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; return result; } //------------------------------------------------------------------------------ @@ -1046,414 +1234,490 @@ bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) void ClipperBase::Clear() { DisposeLocalMinimaList(); - for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i]; + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + TEdge* edges = m_edges[i]; + delete [] edges; + } m_edges.clear(); m_UseFullRange = false; + m_HasOpenPaths = false; } //------------------------------------------------------------------------------ void ClipperBase::Reset() { - m_CurrentLM = m_MinimaList; - if( !m_CurrentLM ) return; //ie nothing to process + m_CurrentLM = m_MinimaList.begin(); + if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process + std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + m_Scanbeam = ScanbeamList(); //clears/resets priority_queue //reset all edges ... - LocalMinima* lm = m_MinimaList; - while( lm ) + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) { - TEdge* e = lm->leftBound; - while( e ) + InsertScanbeam(lm->Y); + TEdge* e = lm->LeftBound; + if (e) { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - e->side = esLeft; - e->outIdx = -1; - e = e->nextInLML; + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; } - e = lm->rightBound; - while( e ) + + e = lm->RightBound; + if (e) { - e->xcurr = e->xbot; - e->ycurr = e->ybot; - e->side = esRight; - e->outIdx = -1; - e = e->nextInLML; + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; } - lm = lm->next; } + m_ActiveEdges = 0; + m_CurrentLM = m_MinimaList.begin(); } //------------------------------------------------------------------------------ void ClipperBase::DisposeLocalMinimaList() { - while( m_MinimaList ) - { - LocalMinima* tmpLm = m_MinimaList->next; - delete m_MinimaList; - m_MinimaList = tmpLm; - } - m_CurrentLM = 0; + m_MinimaList.clear(); + m_CurrentLM = m_MinimaList.begin(); } //------------------------------------------------------------------------------ -void ClipperBase::PopLocalMinima() +bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) { - if( ! m_CurrentLM ) return; - m_CurrentLM = m_CurrentLM->next; + if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; + locMin = &(*m_CurrentLM); + ++m_CurrentLM; + return true; } //------------------------------------------------------------------------------ IntRect ClipperBase::GetBounds() { IntRect result; - LocalMinima* lm = m_MinimaList; - if (!lm) + MinimaList::iterator lm = m_MinimaList.begin(); + if (lm == m_MinimaList.end()) { result.left = result.top = result.right = result.bottom = 0; return result; } - result.left = lm->leftBound->xbot; - result.top = lm->leftBound->ybot; - result.right = lm->leftBound->xbot; - result.bottom = lm->leftBound->ybot; - while (lm) + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm != m_MinimaList.end()) { - if (lm->leftBound->ybot > result.bottom) - result.bottom = lm->leftBound->ybot; - TEdge* e = lm->leftBound; + //todo - needs fixing for open paths + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + TEdge* e = lm->LeftBound; for (;;) { TEdge* bottomE = e; - while (e->nextInLML) + while (e->NextInLML) { - if (e->xbot < result.left) result.left = e->xbot; - if (e->xbot > result.right) result.right = e->xbot; - e = e->nextInLML; + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; } - if (e->xbot < result.left) result.left = e->xbot; - if (e->xbot > result.right) result.right = e->xbot; - if (e->xtop < result.left) result.left = e->xtop; - if (e->xtop > result.right) result.right = e->xtop; - if (e->ytop < result.top) result.top = e->ytop; - - if (bottomE == lm->leftBound) e = lm->rightBound; + result.left = std::min(result.left, e->Bot.X); + result.right = std::max(result.right, e->Bot.X); + result.left = std::min(result.left, e->Top.X); + result.right = std::max(result.right, e->Top.X); + result.top = std::min(result.top, e->Top.Y); + if (bottomE == lm->LeftBound) e = lm->RightBound; else break; } - lm = lm->next; + ++lm; } return result; } - - -//------------------------------------------------------------------------------ -// TClipper methods ... //------------------------------------------------------------------------------ -Clipper::Clipper() : ClipperBase() //constructor +void ClipperBase::InsertScanbeam(const cInt Y) { - m_Scanbeam = 0; - m_ActiveEdges = 0; - m_SortedEdges = 0; - m_IntersectNodes = 0; - m_ExecuteLocked = false; - m_UseFullRange = false; - m_ReverseOutput = false; + m_Scanbeam.push(Y); } //------------------------------------------------------------------------------ -Clipper::~Clipper() //destructor +bool ClipperBase::PopScanbeam(cInt &Y) { - Clear(); - DisposeScanbeamList(); + if (m_Scanbeam.empty()) return false; + Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. + return true; } //------------------------------------------------------------------------------ -void Clipper::Clear() -{ - if (m_edges.size() == 0) return; //avoids problems with ClipperBase destructor - DisposeAllPolyPts(); - ClipperBase::Clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeScanbeamList() -{ - while ( m_Scanbeam ) { - Scanbeam* sb2 = m_Scanbeam->next; - delete m_Scanbeam; - m_Scanbeam = sb2; - } -} -//------------------------------------------------------------------------------ - -void Clipper::Reset() -{ - ClipperBase::Reset(); - m_Scanbeam = 0; - m_ActiveEdges = 0; - m_SortedEdges = 0; - DisposeAllPolyPts(); - LocalMinima* lm = m_MinimaList; - while (lm) - { - InsertScanbeam(lm->Y); - InsertScanbeam(lm->leftBound->ytop); - lm = lm->next; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, Polygons &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - bool succeeded = ExecuteInternal(false); - if (succeeded) BuildResult(solution); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, ExPolygons &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - bool succeeded = ExecuteInternal(true); - if (succeeded) BuildResultEx(solution); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool PolySort(OutRec *or1, OutRec *or2) -{ - if (or1 == or2) return false; - if (!or1->pts || !or2->pts) - { - if (or1->pts != or2->pts) - { - return or1->pts ? true : false; - } - else return false; - } - int i1, i2; - if (or1->isHole) - i1 = or1->FirstLeft->idx; else - i1 = or1->idx; - if (or2->isHole) - i2 = or2->FirstLeft->idx; else - i2 = or2->idx; - int result = i1 - i2; - if (result == 0 && (or1->isHole != or2->isHole)) - { - return or1->isHole ? false : true; - } - else return result < 0; -} -//------------------------------------------------------------------------------ - -OutRec* FindAppendLinkEnd(OutRec *outRec) -{ - while (outRec->AppendLink) outRec = outRec->AppendLink; - return outRec; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec *outRec) -{ - OutRec *tmp; - if (outRec->bottomPt) - tmp = m_PolyOuts[outRec->bottomPt->idx]->FirstLeft; - else - tmp = outRec->FirstLeft; - if (outRec == tmp) throw clipperException("HoleLinkage error"); - - if (tmp) - { - if (tmp->AppendLink) tmp = FindAppendLinkEnd(tmp); - if (tmp == outRec) tmp = 0; - else if (tmp->isHole) - { - FixHoleLinkage(tmp); - tmp = tmp->FirstLeft; - } - } - outRec->FirstLeft = tmp; - if (!tmp) outRec->isHole = false; - outRec->AppendLink = 0; -} -//------------------------------------------------------------------------------ - -bool Clipper::ExecuteInternal(bool fixHoleLinkages) -{ - bool succeeded; - try { - Reset(); - if (!m_CurrentLM ) return true; - long64 botY = PopScanbeam(); - do { - InsertLocalMinimaIntoAEL(botY); - ClearHorzJoins(); - ProcessHorizontals(); - long64 topY = PopScanbeam(); - succeeded = ProcessIntersections(botY, topY); - if (!succeeded) break; - ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - } while( m_Scanbeam ); - } - catch(...) { - succeeded = false; - } - - if (succeeded) - { - //tidy up output polygons and fix orientations where necessary ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->pts) continue; - FixupOutPolygon(*outRec); - if (!outRec->pts) continue; - if (outRec->isHole && fixHoleLinkages) FixHoleLinkage(outRec); - - if (outRec->bottomPt == outRec->bottomFlag && - (Orientation(outRec, m_UseFullRange) != (Area(*outRec, m_UseFullRange) > 0))) - DisposeBottomPt(*outRec); - - if (outRec->isHole == - (m_ReverseOutput ^ Orientation(outRec, m_UseFullRange))) - ReversePolyPtLinks(*outRec->pts); - } - - JoinCommonEdges(fixHoleLinkages); - if (fixHoleLinkages) - std::sort(m_PolyOuts.begin(), m_PolyOuts.end(), PolySort); - } - - ClearJoins(); - ClearHorzJoins(); - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::InsertScanbeam(const long64 Y) -{ - if( !m_Scanbeam ) - { - m_Scanbeam = new Scanbeam; - m_Scanbeam->next = 0; - m_Scanbeam->Y = Y; - } - else if( Y > m_Scanbeam->Y ) - { - Scanbeam* newSb = new Scanbeam; - newSb->Y = Y; - newSb->next = m_Scanbeam; - m_Scanbeam = newSb; - } else - { - Scanbeam* sb2 = m_Scanbeam; - while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next; - if( Y == sb2->Y ) return; //ie ignores duplicates - Scanbeam* newSb = new Scanbeam; - newSb->Y = Y; - newSb->next = sb2->next; - sb2->next = newSb; - } -} -//------------------------------------------------------------------------------ - -long64 Clipper::PopScanbeam() -{ - long64 Y = m_Scanbeam->Y; - Scanbeam* sb2 = m_Scanbeam; - m_Scanbeam = m_Scanbeam->next; - delete sb2; - return Y; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeAllPolyPts(){ +void ClipperBase::DisposeAllOutRecs(){ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) DisposeOutRec(i); m_PolyOuts.clear(); } //------------------------------------------------------------------------------ -void Clipper::DisposeOutRec(PolyOutList::size_type index) +void ClipperBase::DisposeOutRec(PolyOutList::size_type index) { OutRec *outRec = m_PolyOuts[index]; - if (outRec->pts) DisposeOutPts(outRec->pts); + if (outRec->Pts) DisposeOutPts(outRec->Pts); delete outRec; m_PolyOuts[index] = 0; } //------------------------------------------------------------------------------ -void Clipper::SetWindingCount(TEdge &edge) +void ClipperBase::DeleteFromAEL(TEdge *e) { - TEdge *e = edge.prevInAEL; - //find the edge of the same polytype that immediately preceeds 'edge' in AEL - while ( e && e->polyType != edge.polyType ) e = e->prevInAEL; - if ( !e ) + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted + if (AelPrev) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +OutRec* ClipperBase::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size() - 1; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if (Edge1->NextInAEL == Edge2) { - edge.windCnt = edge.windDelta; - edge.windCnt2 = 0; - e = m_ActiveEdges; //ie get ready to calc windCnt2 - } else if ( IsEvenOddFillType(edge) ) + TEdge* Next = Edge2->NextInAEL; + if (Next) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if (Edge2->NextInAEL == Edge1) { - //EvenOdd filling ... - edge.windCnt = 1; - edge.windCnt2 = e->windCnt2; - e = e->nextInAEL; //ie get ready to calc windCnt2 - } else + TEdge* Next = Edge1->NextInAEL; + if (Next) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else { - //nonZero, Positive or Negative filling ... - if ( e->windCnt * e->windDelta < 0 ) - { - if (Abs(e->windCnt) > 1) - { - if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt; - else edge.windCnt = e->windCnt + edge.windDelta; - } else - edge.windCnt = e->windCnt + e->windDelta + edge.windDelta; - } else - { - if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0) - edge.windCnt = e->windCnt; - else if ( e->windCnt + edge.windDelta == 0 ) - edge.windCnt = e->windCnt; - else edge.windCnt = e->windCnt + edge.windDelta; - } - edge.windCnt2 = e->windCnt2; - e = e->nextInAEL; //ie get ready to calc windCnt2 + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; } - //update windCnt2 ... - if ( IsEvenOddAltFillType(edge) ) + if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; + else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) +{ + if (!e->NextInLML) + throw clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::LocalMinimaPending() +{ + return (m_CurrentLM != m_MinimaList.end()); +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) : ClipperBase() //constructor +{ + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(ZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) +{ + return Execute(clipType, solution, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) +{ + return Execute(clipType, polytree, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is needed for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + m_Maxima = MaximaList(); + m_SortedEdges = 0; + + succeeded = true; + cInt botY = 0, topY = 0; + if (!PopScanbeam(botY)) return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(topY) || LocalMinimaPending()) + { + ProcessHorizontals(); + ClearGhostJoins(); + if (!ProcessIntersections(topY)) + { + succeeded = false; + break; + } + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + InsertLocalMinimaIntoAEL(botY); + } + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts) continue; + if (outRec->IsOpen) + FixupOutPolyline(*outRec); + else + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + if (edge.WindDelta == 0) + { + PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); + edge.WindCnt = (pft == pftNegative ? -1 : 1); + } + else + edge.WindCnt = edge.WindDelta; + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) { //EvenOdd filling ... - while ( e != &edge ) + if (edge.WindDelta == 0) { - edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; - e = e->nextInAEL; + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; } } else { //nonZero, Positive or Negative filling ... while ( e != &edge ) { - edge.windCnt2 += e->windDelta; - e = e->nextInAEL; + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; } } } @@ -1461,7 +1725,7 @@ void Clipper::SetWindingCount(TEdge &edge) bool Clipper::IsEvenOddFillType(const TEdge& edge) const { - if (edge.polyType == ptSubject) + if (edge.PolyTyp == ptSubject) return m_SubjFillType == pftEvenOdd; else return m_ClipFillType == pftEvenOdd; } @@ -1469,7 +1733,7 @@ bool Clipper::IsEvenOddFillType(const TEdge& edge) const bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const { - if (edge.polyType == ptSubject) + if (edge.PolyTyp == ptSubject) return m_ClipFillType == pftEvenOdd; else return m_SubjFillType == pftEvenOdd; } @@ -1478,7 +1742,7 @@ bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const bool Clipper::IsContributing(const TEdge& edge) const { PolyFillType pft, pft2; - if (edge.polyType == ptSubject) + if (edge.PolyTyp == ptSubject) { pft = m_SubjFillType; pft2 = m_ClipFillType; @@ -1491,14 +1755,17 @@ bool Clipper::IsContributing(const TEdge& edge) const switch(pft) { case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; case pftNonZero: - if (Abs(edge.windCnt) != 1) return false; + if (Abs(edge.WindCnt) != 1) return false; break; case pftPositive: - if (edge.windCnt != 1) return false; + if (edge.WindCnt != 1) return false; break; default: //pftNegative - if (edge.windCnt != -1) return false; + if (edge.WindCnt != -1) return false; } switch(m_ClipType) @@ -1508,94 +1775,123 @@ bool Clipper::IsContributing(const TEdge& edge) const { case pftEvenOdd: case pftNonZero: - return (edge.windCnt2 != 0); + return (edge.WindCnt2 != 0); case pftPositive: - return (edge.windCnt2 > 0); + return (edge.WindCnt2 > 0); default: - return (edge.windCnt2 < 0); + return (edge.WindCnt2 < 0); } + break; case ctUnion: switch(pft2) { case pftEvenOdd: case pftNonZero: - return (edge.windCnt2 == 0); + return (edge.WindCnt2 == 0); case pftPositive: - return (edge.windCnt2 <= 0); + return (edge.WindCnt2 <= 0); default: - return (edge.windCnt2 >= 0); + return (edge.WindCnt2 >= 0); } + break; case ctDifference: - if (edge.polyType == ptSubject) + if (edge.PolyTyp == ptSubject) switch(pft2) { case pftEvenOdd: case pftNonZero: - return (edge.windCnt2 == 0); + return (edge.WindCnt2 == 0); case pftPositive: - return (edge.windCnt2 <= 0); + return (edge.WindCnt2 <= 0); default: - return (edge.windCnt2 >= 0); + return (edge.WindCnt2 >= 0); } else switch(pft2) { case pftEvenOdd: case pftNonZero: - return (edge.windCnt2 != 0); + return (edge.WindCnt2 != 0); case pftPositive: - return (edge.windCnt2 > 0); + return (edge.WindCnt2 > 0); default: - return (edge.windCnt2 < 0); + return (edge.WindCnt2 < 0); } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; default: return true; } } //------------------------------------------------------------------------------ -void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { + OutPt* result; TEdge *e, *prevE; - if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) ) + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) { - AddOutPt( e1, pt ); - e2->outIdx = e1->outIdx; - e1->side = esLeft; - e2->side = esRight; + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; e = e1; - if (e->prevInAEL == e2) - prevE = e2->prevInAEL; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; else - prevE = e->prevInAEL; + prevE = e->PrevInAEL; } else { - AddOutPt( e2, pt ); - e1->outIdx = e2->outIdx; - e1->side = esRight; - e2->side = esLeft; + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; e = e2; - if (e->prevInAEL == e1) - prevE = e1->prevInAEL; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; else - prevE = e->prevInAEL; + prevE = e->PrevInAEL; } - if (prevE && prevE->outIdx >= 0 && - (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) && - SlopesEqual(*e, *prevE, m_UseFullRange)) - AddJoin(e, prevE, -1, -1); + + if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) + { + cInt xPrev = TopX(*prevE, Pt.Y); + cInt xE = TopX(*e, Pt.Y); + if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && + SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + } + return result; } //------------------------------------------------------------------------------ -void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { - AddOutPt( e1, pt ); - if( e1->outIdx == e2->outIdx ) + AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); + if( e1->OutIdx == e2->OutIdx ) { - e1->outIdx = -1; - e2->outIdx = -1; + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; } - else if (e1->outIdx < e2->outIdx) + else if (e1->OutIdx < e2->OutIdx) AppendPolygon(e1, e2); else AppendPolygon(e2, e1); @@ -1609,50 +1905,48 @@ void Clipper::AddEdgeToSEL(TEdge *edge) if( !m_SortedEdges ) { m_SortedEdges = edge; - edge->prevInSEL = 0; - edge->nextInSEL = 0; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; } else { - edge->nextInSEL = m_SortedEdges; - edge->prevInSEL = 0; - m_SortedEdges->prevInSEL = edge; + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; m_SortedEdges = edge; } } //------------------------------------------------------------------------------ +bool Clipper::PopEdgeFromSEL(TEdge *&edge) +{ + if (!m_SortedEdges) return false; + edge = m_SortedEdges; + DeleteFromSEL(m_SortedEdges); + return true; +} +//------------------------------------------------------------------------------ + void Clipper::CopyAELToSEL() { TEdge* e = m_ActiveEdges; m_SortedEdges = e; - if (!m_ActiveEdges) return; - m_SortedEdges->prevInSEL = 0; - e = e->nextInAEL; while ( e ) { - e->prevInSEL = e->prevInAEL; - e->prevInSEL->nextInSEL = e; - e->nextInSEL = 0; - e = e->nextInAEL; + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; } } //------------------------------------------------------------------------------ -void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx) +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) { - JoinRec* jr = new JoinRec; - if (e1OutIdx >= 0) - jr->poly1Idx = e1OutIdx; else - jr->poly1Idx = e1->outIdx; - jr->pt1a = IntPoint(e1->xcurr, e1->ycurr); - jr->pt1b = IntPoint(e1->xtop, e1->ytop); - if (e2OutIdx >= 0) - jr->poly2Idx = e2OutIdx; else - jr->poly2Idx = e2->outIdx; - jr->pt2a = IntPoint(e2->xcurr, e2->ycurr); - jr->pt2b = IntPoint(e2->xtop, e2->ytop); - m_Joins.push_back(jr); + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); } //------------------------------------------------------------------------------ @@ -1664,164 +1958,236 @@ void Clipper::ClearJoins() } //------------------------------------------------------------------------------ -void Clipper::AddHorzJoin(TEdge *e, int idx) +void Clipper::ClearGhostJoins() { - HorzJoinRec* hj = new HorzJoinRec; - hj->edge = e; - hj->savedIdx = idx; - m_HorizJoins.push_back(hj); + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); } //------------------------------------------------------------------------------ -void Clipper::ClearHorzJoins() +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) { - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++) - delete m_HorizJoins[i]; - m_HorizJoins.resize(0); + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); } //------------------------------------------------------------------------------ -void Clipper::InsertLocalMinimaIntoAEL( const long64 botY) +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { - while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + const LocalMinimum *lm; + while (PopLocalMinima(botY, lm)) { - TEdge* lb = m_CurrentLM->leftBound; - TEdge* rb = m_CurrentLM->rightBound; - - InsertEdgeIntoAEL( lb ); - InsertScanbeam( lb->ytop ); - InsertEdgeIntoAEL( rb ); - - if (IsEvenOddFillType(*lb)) + TEdge* lb = lm->LeftBound; + TEdge* rb = lm->RightBound; + + OutPt *Op1 = 0; + if (!lb) { - lb->windDelta = 1; - rb->windDelta = 1; + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } + else if (!rb) + { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); } else { - rb->windDelta = -lb->windDelta; + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); } - SetWindingCount( *lb ); - rb->windCnt = lb->windCnt; - rb->windCnt2 = lb->windCnt2; - if( NEAR_EQUAL(rb->dx, HORIZONTAL) ) - { - //nb: only rightbounds can have a horizontal bottom edge - AddEdgeToSEL( rb ); - InsertScanbeam( rb->nextInLML->ytop ); - } - else - InsertScanbeam( rb->ytop ); + if (rb) + { + if (IsHorizontal(*rb)) + { + AddEdgeToSEL(rb); + if (rb->NextInLML) + InsertScanbeam(rb->NextInLML->Top.Y); + } + else InsertScanbeam( rb->Top.Y ); + } - if( IsContributing(*lb) ) - AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); + if (!lb || !rb) continue; //if any output polygons share an edge, they'll need joining later ... - if (rb->outIdx >= 0) + if (Op1 && IsHorizontal(*rb) && + m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) { - if (NEAR_EQUAL(rb->dx, HORIZONTAL)) + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) { - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) { - IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. - HorzJoinRec* hj = m_HorizJoins[i]; - //if horizontals rb and hj.edge overlap, flag for joining later ... - if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), - IntPoint(hj->edge->xtop, hj->edge->ytop), - IntPoint(rb->xbot, rb->ybot), - IntPoint(rb->xtop, rb->ytop), pt, pt2)) - AddJoin(hj->edge, rb, hj->savedIdx); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; } } } - - if( lb->nextInAEL != rb ) - { - if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 && - SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange)) - AddJoin(rb, rb->prevInAEL); - - TEdge* e = lb->nextInAEL; - IntPoint pt = IntPoint(lb->xcurr, lb->ycurr); - while( e != rb ) - { - if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); - //nb: For calculating winding counts etc, IntersectEdges() assumes - //that param1 will be to the right of param2 ABOVE the intersection ... - IntersectEdges( rb , e , pt , ipNone); //order important here - e = e->nextInAEL; - } - } - PopLocalMinima(); + } } //------------------------------------------------------------------------------ -void Clipper::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->prevInAEL; - TEdge* AelNext = e->nextInAEL; - if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted - if( AelPrev ) AelPrev->nextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if( AelNext ) AelNext->prevInAEL = AelPrev; - e->nextInAEL = 0; - e->prevInAEL = 0; -} -//------------------------------------------------------------------------------ - void Clipper::DeleteFromSEL(TEdge *e) { - TEdge* SelPrev = e->prevInSEL; - TEdge* SelNext = e->nextInSEL; + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted - if( SelPrev ) SelPrev->nextInSEL = SelNext; + if( SelPrev ) SelPrev->NextInSEL = SelNext; else m_SortedEdges = SelNext; - if( SelNext ) SelNext->prevInSEL = SelPrev; - e->nextInSEL = 0; - e->prevInSEL = 0; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; } //------------------------------------------------------------------------------ -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, IntersectProtects protects) +#ifdef use_xyz +void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { - //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before - //e2 in AEL except when e1 is being inserted at the intersection point ... - bool e1stops = !(ipLeft & protects) && !e1->nextInLML && - e1->xtop == pt.X && e1->ytop == pt.Y; - bool e2stops = !(ipRight & protects) && !e2->nextInLML && - e2->xtop == pt.X && e2->ytop == pt.Y; - bool e1Contributing = ( e1->outIdx >= 0 ); - bool e2contributing = ( e2->outIdx >= 0 ); + if (pt.Z != 0 || !m_ZFill) return; + else if (pt == e1.Bot) pt.Z = e1.Bot.Z; + else if (pt == e1.Top) pt.Z = e1.Top.Z; + else if (pt == e2.Bot) pt.Z = e2.Bot.Z; + else if (pt == e2.Top) pt.Z = e2.Top.Z; + else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) +{ + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) return; + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + return; + } +#endif //update winding counts... - //assumes that e1 will be to the right of e2 ABOVE the intersection - if ( e1->polyType == e2->polyType ) + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) { if ( IsEvenOddFillType( *e1) ) { - int oldE1WindCnt = e1->windCnt; - e1->windCnt = e2->windCnt; - e2->windCnt = oldE1WindCnt; + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; } else { - if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt; - else e1->windCnt += e2->windDelta; - if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt; - else e2->windCnt -= e1->windDelta; + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; } } else { - if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta; - else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0; - if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta; - else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; } PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; - if (e1->polyType == ptSubject) + if (e1->PolyTyp == ptSubject) { e1FillType = m_SubjFillType; e1FillType2 = m_ClipFillType; @@ -1830,7 +2196,7 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, e1FillType = m_ClipFillType; e1FillType2 = m_SubjFillType; } - if (e2->polyType == ptSubject) + if (e2->PolyTyp == ptSubject) { e2FillType = m_SubjFillType; e2FillType2 = m_ClipFillType; @@ -1840,134 +2206,146 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, e2FillType2 = m_SubjFillType; } - long64 e1Wc, e2Wc; + cInt e1Wc, e2Wc; switch (e1FillType) { - case pftPositive: e1Wc = e1->windCnt; break; - case pftNegative: e1Wc = -e1->windCnt; break; - default: e1Wc = Abs(e1->windCnt); + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); } switch(e2FillType) { - case pftPositive: e2Wc = e2->windCnt; break; - case pftNegative: e2Wc = -e2->windCnt; break; - default: e2Wc = Abs(e2->windCnt); + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); } - if ( e1Contributing && e2contributing ) + if ( e1Contributing && e2Contributing ) { - if ( e1stops || e2stops || - (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1->polyType != e2->polyType && m_ClipType != ctXor) ) - AddLocalMaxPoly(e1, e2, pt); + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + { + AddLocalMaxPoly(e1, e2, Pt); + } else - DoBothEdges( e1, e2, pt ); + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } } else if ( e1Contributing ) { - if ((e2Wc == 0 || e2Wc == 1) && - (m_ClipType != ctIntersection || - e2->polyType == ptSubject || (e2->windCnt2 != 0))) - DoEdge1(e1, e2, pt); + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } } - else if ( e2contributing ) + else if ( e2Contributing ) { - if ((e1Wc == 0 || e1Wc == 1) && - (m_ClipType != ctIntersection || - e1->polyType == ptSubject || (e1->windCnt2 != 0))) - DoEdge2(e1, e2, pt); + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } } - else if ( (e1Wc == 0 || e1Wc == 1) && - (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { //neither edge is currently contributing ... - long64 e1Wc2, e2Wc2; + cInt e1Wc2, e2Wc2; switch (e1FillType2) { - case pftPositive: e1Wc2 = e1->windCnt2; break; - case pftNegative : e1Wc2 = -e1->windCnt2; break; - default: e1Wc2 = Abs(e1->windCnt2); + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); } switch (e2FillType2) { - case pftPositive: e2Wc2 = e2->windCnt2; break; - case pftNegative: e2Wc2 = -e2->windCnt2; break; - default: e2Wc2 = Abs(e2->windCnt2); + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); } - if (e1->polyType != e2->polyType) - AddLocalMinPoly(e1, e2, pt); + if (e1->PolyTyp != e2->PolyTyp) + { + AddLocalMinPoly(e1, e2, Pt); + } else if (e1Wc == 1 && e2Wc == 1) switch( m_ClipType ) { case ctIntersection: if (e1Wc2 > 0 && e2Wc2 > 0) - AddLocalMinPoly(e1, e2, pt); + AddLocalMinPoly(e1, e2, Pt); break; case ctUnion: if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) - AddLocalMinPoly(e1, e2, pt); + AddLocalMinPoly(e1, e2, Pt); break; case ctDifference: - if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - AddLocalMinPoly(e1, e2, pt); + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); break; case ctXor: - AddLocalMinPoly(e1, e2, pt); + AddLocalMinPoly(e1, e2, Pt); } else SwapSides( *e1, *e2 ); } - - if( (e1stops != e2stops) && - ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) ) - { - SwapSides( *e1, *e2 ); - SwapPolyIndexes( *e1, *e2 ); - } - - //finally, delete any non-contributing maxima edges ... - if( e1stops ) DeleteFromAEL( e1 ); - if( e2stops ) DeleteFromAEL( e2 ); } //------------------------------------------------------------------------------ -void Clipper::SetHoleState(TEdge *e, OutRec *outRec) +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { - bool isHole = false; - TEdge *e2 = e->prevInAEL; + TEdge *e2 = e->PrevInAEL; + TEdge *eTmp = 0; while (e2) { - if (e2->outIdx >= 0) + if (e2->OutIdx >= 0 && e2->WindDelta != 0) { - isHole = !isHole; - if (! outRec->FirstLeft) - outRec->FirstLeft = m_PolyOuts[e2->outIdx]; + if (!eTmp) eTmp = e2; + else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; } - e2 = e2->prevInAEL; + e2 = e2->PrevInAEL; + } + if (!eTmp) + { + outrec->FirstLeft = 0; + outrec->IsHole = false; + } + else + { + outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; + outrec->IsHole = !outrec->FirstLeft->IsHole; } - if (isHole) outRec->isHole = true; } //------------------------------------------------------------------------------ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) { //work out which polygon fragment has the correct hole state ... - OutPt *outPt1 = outRec1->bottomPt; - OutPt *outPt2 = outRec2->bottomPt; - if (outPt1->pt.Y > outPt2->pt.Y) return outRec1; - else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2; - else if (outPt1->pt.X < outPt2->pt.X) return outRec1; - else if (outPt1->pt.X > outPt2->pt.X) return outRec2; - else if (outPt1->next == outPt1) return outRec2; - else if (outPt2->next == outPt2) return outRec1; - else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1; + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; else return outRec2; } //------------------------------------------------------------------------------ -bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) { do { @@ -1978,1483 +2356,2271 @@ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) } //------------------------------------------------------------------------------ +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->outIdx]; - OutRec *outRec2 = m_PolyOuts[e2->outIdx]; + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; - if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); + if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); - OutPt* p1_lft = outRec1->pts; - OutPt* p1_rt = p1_lft->prev; - OutPt* p2_lft = outRec2->pts; - OutPt* p2_rt = p2_lft->prev; - - EdgeSide side; + //get the start and ends of both output polygons and //join e2 poly onto e1 poly and delete pointers to e2 ... - if( e1->side == esLeft ) + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) { - if( e2->side == esLeft ) + if( e2->Side == esLeft ) { //z y x a b c - ReversePolyPtLinks(*p2_lft); - p2_lft->next = p1_lft; - p1_lft->prev = p2_lft; - p1_rt->next = p2_rt; - p2_rt->prev = p1_rt; - outRec1->pts = p2_rt; + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; } else { //x y z a b c - p2_rt->next = p1_lft; - p1_lft->prev = p2_rt; - p2_lft->prev = p1_rt; - p1_rt->next = p2_lft; - outRec1->pts = p2_lft; + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; } - side = esLeft; } else { - if( e2->side == esRight ) + if( e2->Side == esRight ) { //a b c z y x - ReversePolyPtLinks( *p2_lft ); - p1_rt->next = p2_rt; - p2_rt->prev = p1_rt; - p2_lft->next = p1_lft; - p1_lft->prev = p2_lft; + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; } else { //a b c x y z - p1_rt->next = p2_lft; - p2_lft->prev = p1_rt; - p1_lft->prev = p2_rt; - p2_rt->next = p1_lft; + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; } - side = esRight; } + outRec1->BottomPt = 0; if (holeStateRec == outRec2) { - outRec1->bottomPt = outRec2->bottomPt; - outRec1->bottomPt->idx = outRec1->idx; if (outRec2->FirstLeft != outRec1) outRec1->FirstLeft = outRec2->FirstLeft; - outRec1->isHole = outRec2->isHole; + outRec1->IsHole = outRec2->IsHole; } - outRec2->pts = 0; - outRec2->bottomPt = 0; - outRec2->AppendLink = outRec1; - int OKIdx = e1->outIdx; - int ObsoleteIdx = e2->outIdx; + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; - e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly - e2->outIdx = -1; + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; TEdge* e = m_ActiveEdges; while( e ) { - if( e->outIdx == ObsoleteIdx ) + if( e->OutIdx == ObsoleteIdx ) { - e->outIdx = OKIdx; - e->side = side; + e->OutIdx = OKIdx; + e->Side = e1->Side; break; } - e = e->nextInAEL; - } - - for (JoinList::size_type i = 0; i < m_Joins.size(); ++i) - { - if (m_Joins[i]->poly1Idx == ObsoleteIdx) m_Joins[i]->poly1Idx = OKIdx; - if (m_Joins[i]->poly2Idx == ObsoleteIdx) m_Joins[i]->poly2Idx = OKIdx; - } - - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - if (m_HorizJoins[i]->savedIdx == ObsoleteIdx) - m_HorizJoins[i]->savedIdx = OKIdx; + e = e->NextInAEL; } + outRec2->Idx = outRec1->Idx; } //------------------------------------------------------------------------------ -OutRec* Clipper::CreateOutRec() +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { - OutRec* result = new OutRec; - result->isHole = false; - result->FirstLeft = 0; - result->AppendLink = 0; - result->pts = 0; - result->bottomPt = 0; - result->sides = esNeither; - result->bottomFlag = 0; - - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeBottomPt(OutRec &outRec) -{ - OutPt* next = outRec.bottomPt->next; - OutPt* prev = outRec.bottomPt->prev; - if (outRec.pts == outRec.bottomPt) outRec.pts = next; - delete outRec.bottomPt; - next->prev = prev; - prev->next = next; - outRec.bottomPt = next; - FixupOutPolygon(outRec); -} -//------------------------------------------------------------------------------ - -void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) -{ - bool ToFront = (e->side == esLeft); - if( e->outIdx < 0 ) + if( e->OutIdx < 0 ) { OutRec *outRec = CreateOutRec(); - m_PolyOuts.push_back(outRec); - outRec->idx = (int)m_PolyOuts.size()-1; - e->outIdx = outRec->idx; - OutPt* op = new OutPt; - outRec->pts = op; - outRec->bottomPt = op; - op->pt = pt; - op->idx = outRec->idx; - op->next = op; - op->prev = op; - SetHoleState(e, outRec); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); + e->OutIdx = outRec->Idx; + return newOp; } else { - OutRec *outRec = m_PolyOuts[e->outIdx]; - OutPt* op = outRec->pts; - if ((ToFront && PointsEqual(pt, op->pt)) || - (!ToFront && PointsEqual(pt, op->prev->pt))) return; + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; - if ((e->side | outRec->sides) != outRec->sides) - { - //check for 'rounding' artefacts ... - if (outRec->sides == esNeither && pt.Y == op->pt.Y) - { - if (ToFront) - { - if (pt.X == op->pt.X +1) return; //ie wrong side of bottomPt - } - else if (pt.X == op->pt.X -1) return; //ie wrong side of bottomPt - } + bool ToFront = (e->Side == esLeft); + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; - outRec->sides = (EdgeSide)(outRec->sides | e->side); - if (outRec->sides == esBoth) - { - //A vertex from each side has now been added. - //Vertices of one side of an output polygon are quite commonly close to - //or even 'touching' edges of the other side of the output polygon. - //Very occasionally vertices from one side can 'cross' an edge on the - //the other side. The distance 'crossed' is always less that a unit - //and is purely an artefact of coordinate rounding. Nevertheless, this - //results in very tiny self-intersections. Because of the way - //orientation is calculated, even tiny self-intersections can cause - //the Orientation function to return the wrong result. Therefore, it's - //important to ensure that any self-intersections close to BottomPt are - //detected and removed before orientation is assigned. - - OutPt *opBot, *op2; - if (ToFront) - { - opBot = outRec->pts; - op2 = opBot->next; //op2 == right side - if (opBot->pt.Y != op2->pt.Y && opBot->pt.Y != pt.Y && - ((opBot->pt.X - pt.X)/(opBot->pt.Y - pt.Y) < - (opBot->pt.X - op2->pt.X)/(opBot->pt.Y - op2->pt.Y))) - outRec->bottomFlag = opBot; - } else - { - opBot = outRec->pts->prev; - op2 = opBot->prev; //op2 == left side - if (opBot->pt.Y != op2->pt.Y && opBot->pt.Y != pt.Y && - ((opBot->pt.X - pt.X)/(opBot->pt.Y - pt.Y) > - (opBot->pt.X - op2->pt.X)/(opBot->pt.Y - op2->pt.Y))) - outRec->bottomFlag = opBot; - } - } - } - - OutPt* op2 = new OutPt; - op2->pt = pt; - op2->idx = outRec->idx; - if (op2->pt.Y == outRec->bottomPt->pt.Y && - op2->pt.X < outRec->bottomPt->pt.X) - outRec->bottomPt = op2; - op2->next = op; - op2->prev = op->prev; - op2->prev->next = op2; - op->prev = op2; - if (ToFront) outRec->pts = op2; + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; + return newOp; } } //------------------------------------------------------------------------------ +OutPt* Clipper::GetLastOutPt(TEdge *e) +{ + OutRec *outRec = m_PolyOuts[e->OutIdx]; + if (e->Side == esLeft) + return outRec->Pts; + else + return outRec->Pts->Prev; +} +//------------------------------------------------------------------------------ + void Clipper::ProcessHorizontals() { - TEdge* horzEdge = m_SortedEdges; - while( horzEdge ) - { - DeleteFromSEL( horzEdge ); - ProcessHorizontal( horzEdge ); - horzEdge = m_SortedEdges; - } + TEdge* horzEdge; + while (PopEdgeFromSEL(horzEdge)) + ProcessHorizontal(horzEdge); } //------------------------------------------------------------------------------ -bool Clipper::IsTopHorz(const long64 XPos) +inline bool IsMinima(TEdge *e) { - TEdge* e = m_SortedEdges; - while( e ) - { - if( ( XPos >= std::min(e->xcurr, e->xtop) ) && - ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false; - e = e->nextInSEL; - } - return true; + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); } //------------------------------------------------------------------------------ -bool IsMinima(TEdge *e) +inline bool IsMaxima(TEdge *e, const cInt Y) { - return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e); + return e && e->Top.Y == Y && !e->NextInLML; } //------------------------------------------------------------------------------ -bool IsMaxima(TEdge *e, const long64 Y) +inline bool IsIntermediate(TEdge *e, const cInt Y) { - return e && e->ytop == Y && !e->nextInLML; -} -//------------------------------------------------------------------------------ - -bool IsIntermediate(TEdge *e, const long64 Y) -{ - return e->ytop == Y && e->nextInLML; + return e->Top.Y == Y && e->NextInLML; } //------------------------------------------------------------------------------ TEdge *GetMaximaPair(TEdge *e) { - if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop ) - return e->prev; else - return e->next; + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + return e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + return e->Prev; + else return 0; } //------------------------------------------------------------------------------ -void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) +TEdge *GetMaximaPairEx(TEdge *e) { - if( !edge1->nextInAEL && !edge1->prevInAEL ) return; - if( !edge2->nextInAEL && !edge2->prevInAEL ) return; - - if( edge1->nextInAEL == edge2 ) - { - TEdge* next = edge2->nextInAEL; - if( next ) next->prevInAEL = edge1; - TEdge* prev = edge1->prevInAEL; - if( prev ) prev->nextInAEL = edge2; - edge2->prevInAEL = prev; - edge2->nextInAEL = edge1; - edge1->prevInAEL = edge2; - edge1->nextInAEL = next; - } - else if( edge2->nextInAEL == edge1 ) - { - TEdge* next = edge1->nextInAEL; - if( next ) next->prevInAEL = edge2; - TEdge* prev = edge2->prevInAEL; - if( prev ) prev->nextInAEL = edge1; - edge1->prevInAEL = prev; - edge1->nextInAEL = edge2; - edge2->prevInAEL = edge1; - edge2->nextInAEL = next; - } - else - { - TEdge* next = edge1->nextInAEL; - TEdge* prev = edge1->prevInAEL; - edge1->nextInAEL = edge2->nextInAEL; - if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1; - edge1->prevInAEL = edge2->prevInAEL; - if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1; - edge2->nextInAEL = next; - if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2; - edge2->prevInAEL = prev; - if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2; - } - - if( !edge1->prevInAEL ) m_ActiveEdges = edge1; - else if( !edge2->prevInAEL ) m_ActiveEdges = edge2; + //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) + TEdge* result = GetMaximaPair(e); + if (result && (result->OutIdx == Skip || + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; + return result; } //------------------------------------------------------------------------------ -void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2) +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) { - if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return; - if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return; + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; - if( edge1->nextInSEL == edge2 ) + if( Edge1->NextInSEL == Edge2 ) { - TEdge* next = edge2->nextInSEL; - if( next ) next->prevInSEL = edge1; - TEdge* prev = edge1->prevInSEL; - if( prev ) prev->nextInSEL = edge2; - edge2->prevInSEL = prev; - edge2->nextInSEL = edge1; - edge1->prevInSEL = edge2; - edge1->nextInSEL = next; + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; } - else if( edge2->nextInSEL == edge1 ) + else if( Edge2->NextInSEL == Edge1 ) { - TEdge* next = edge1->nextInSEL; - if( next ) next->prevInSEL = edge2; - TEdge* prev = edge2->prevInSEL; - if( prev ) prev->nextInSEL = edge1; - edge1->prevInSEL = prev; - edge1->nextInSEL = edge2; - edge2->prevInSEL = edge1; - edge2->nextInSEL = next; + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; } else { - TEdge* next = edge1->nextInSEL; - TEdge* prev = edge1->prevInSEL; - edge1->nextInSEL = edge2->nextInSEL; - if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1; - edge1->prevInSEL = edge2->prevInSEL; - if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1; - edge2->nextInSEL = next; - if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2; - edge2->prevInSEL = prev; - if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2; + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; } - if( !edge1->prevInSEL ) m_SortedEdges = edge1; - else if( !edge2->prevInSEL ) m_SortedEdges = edge2; + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; } //------------------------------------------------------------------------------ TEdge* GetNextInAEL(TEdge *e, Direction dir) { - return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL; + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; } //------------------------------------------------------------------------------ +void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; - long64 horzLeft, horzRight; + cInt horzLeft, horzRight; + bool IsOpen = (horzEdge->WindDelta == 0); - if( horzEdge->xcurr < horzEdge->xtop ) + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + MaximaList::const_iterator maxIt; + MaximaList::const_reverse_iterator maxRit; + if (m_Maxima.size() > 0) { - horzLeft = horzEdge->xcurr; - horzRight = horzEdge->xtop; - dir = dLeftToRight; - } else - { - horzLeft = horzEdge->xtop; - horzRight = horzEdge->xcurr; - dir = dRightToLeft; - } - - TEdge* eMaxPair; - if( horzEdge->nextInLML ) eMaxPair = 0; - else eMaxPair = GetMaximaPair(horzEdge); - - TEdge* e = GetNextInAEL( horzEdge , dir ); - while( e ) - { - TEdge* eNext = GetNextInAEL( e, dir ); - - if (eMaxPair || - ((dir == dLeftToRight) && (e->xcurr <= horzRight)) || - ((dir == dRightToLeft) && (e->xcurr >= horzLeft))) - { - //ok, so far it looks like we're still in range of the horizontal edge - if ( e->xcurr == horzEdge->xtop && !eMaxPair ) + //get the first maxima in range (X) ... + if (dir == dLeftToRight) { - assert(horzEdge->nextInLML); - if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) - { - //if output polygons share an edge, they'll need joining later ... - if (horzEdge->outIdx >= 0 && e->outIdx >= 0) - AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); - break; //we've reached the end of the horizontal line - } - else if (e->dx < horzEdge->nextInLML->dx) - //we really have got to the end of the intermediate horz edge so quit. - //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. - break; - } - - if( e == eMaxPair ) - { - //horzEdge is evidently a maxima horizontal and we've arrived at its end. - if (dir == dLeftToRight) - IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); - else - IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); - if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); - return; - } - else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) ) - { - //An overlapping horizontal edge. Overlapping horizontal edges are - //processed as if layered with the current horizontal edge (horizEdge) - //being infinitesimally lower that the next (e). Therfore, we - //intersect with e only if e.xcurr is within the bounds of horzEdge ... - if( dir == dLeftToRight ) - IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); - else - IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); - } - else if( dir == dLeftToRight ) - { - IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + maxIt = m_Maxima.begin(); + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; + if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) + maxIt = m_Maxima.end(); } else { - IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), - (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + maxRit = m_Maxima.rbegin(); + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; + if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) + maxRit = m_Maxima.rend(); } - SwapPositionsInAEL( horzEdge, e ); - } - else if( (dir == dLeftToRight && e->xcurr > horzRight && m_SortedEdges) || - (dir == dRightToLeft && e->xcurr < horzLeft && m_SortedEdges) ) break; - e = eNext; - } //end while + } - if( horzEdge->nextInLML ) + OutPt* op1 = 0; + + for (;;) //loop through consec. horizontal edges { - if( horzEdge->outIdx >= 0 ) - AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop)); - UpdateEdgeIntoAEL( horzEdge ); + + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + + //this code block inserts extra coords into horizontal edges (in output + //polygons) whereever maxima touch these horizontal edges. This helps + //'simplifying' polygons (ie if the Simplify property is set). + if (m_Maxima.size() > 0) + { + if (dir == dLeftToRight) + { + while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) + { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); + maxIt++; + } + } + else + { + while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) + { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); + maxRit++; + } + } + }; + + if ((dir == dLeftToRight && e->Curr.X > horzRight) || + (dir == dRightToLeft && e->Curr.X < horzLeft)) break; + + //Also break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times + { +#ifdef use_xyz + if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e); + else SetZ(e->Curr, *e, *horzEdge); +#endif + op1 = AddOutPt(horzEdge, e->Curr); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + } + + //OK, so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (horzEdge->OutIdx >= 0) + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + + if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt); + } + TEdge* eNext = GetNextInAEL(e, dir); + SwapPositionsInAEL( horzEdge, e ); + e = eNext; + } //end while(e) + + //Break out of loop if HorzEdge.NextInLML is not also horizontal ... + if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; + + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + } //end for (;;) + + if (horzEdge->OutIdx >= 0 && !op1) + { + op1 = GetLastOutPt(horzEdge); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Top); + } + + if (horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + op1 = AddOutPt( horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); } else { - if ( horzEdge->outIdx >= 0 ) - IntersectEdges( horzEdge, eMaxPair, - IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth); - assert(eMaxPair); - if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); - DeleteFromAEL(eMaxPair); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); DeleteFromAEL(horzEdge); } } //------------------------------------------------------------------------------ -void Clipper::UpdateEdgeIntoAEL(TEdge *&e) -{ - if( !e->nextInLML ) throw - clipperException("UpdateEdgeIntoAEL: invalid call"); - TEdge* AelPrev = e->prevInAEL; - TEdge* AelNext = e->nextInAEL; - e->nextInLML->outIdx = e->outIdx; - if( AelPrev ) AelPrev->nextInAEL = e->nextInLML; - else m_ActiveEdges = e->nextInLML; - if( AelNext ) AelNext->prevInAEL = e->nextInLML; - e->nextInLML->side = e->side; - e->nextInLML->windDelta = e->windDelta; - e->nextInLML->windCnt = e->windCnt; - e->nextInLML->windCnt2 = e->windCnt2; - e = e->nextInLML; - e->prevInAEL = AelPrev; - e->nextInAEL = AelNext; - if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop ); -} -//------------------------------------------------------------------------------ - -bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) +bool Clipper::ProcessIntersections(const cInt topY) { if( !m_ActiveEdges ) return true; try { - BuildIntersectList(botY, topY); - if ( !m_IntersectNodes) return true; - if ( FixupIntersections() ) ProcessIntersectList(); + BuildIntersectList(topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); else return false; } - catch(...) { + catch(...) + { m_SortedEdges = 0; DisposeIntersectNodes(); throw clipperException("ProcessIntersections error"); } + m_SortedEdges = 0; return true; } //------------------------------------------------------------------------------ void Clipper::DisposeIntersectNodes() { - while ( m_IntersectNodes ) - { - IntersectNode* iNode = m_IntersectNodes->next; - delete m_IntersectNodes; - m_IntersectNodes = iNode; - } + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); } //------------------------------------------------------------------------------ -void Clipper::BuildIntersectList(const long64 botY, const long64 topY) +void Clipper::BuildIntersectList(const cInt topY) { if ( !m_ActiveEdges ) return; //prepare for sorting ... TEdge* e = m_ActiveEdges; - e->tmpX = TopX( *e, topY ); m_SortedEdges = e; - m_SortedEdges->prevInSEL = 0; - e = e->nextInAEL; while( e ) { - e->prevInSEL = e->prevInAEL; - e->prevInSEL->nextInSEL = e; - e->nextInSEL = 0; - e->tmpX = TopX( *e, topY ); - e = e->nextInAEL; + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; } //bubblesort ... - bool isModified = true; - while( isModified && m_SortedEdges ) + bool isModified; + do { isModified = false; e = m_SortedEdges; - while( e->nextInSEL ) + while( e->NextInSEL ) { - TEdge *eNext = e->nextInSEL; - IntPoint pt; - if(e->tmpX > eNext->tmpX && - IntersectPoint(*e, *eNext, pt, m_UseFullRange)) + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) { - if (pt.Y > botY) - { - pt.Y = botY; - pt.X = TopX(*e, pt.Y); - } - AddIntersectNode( e, eNext, pt ); + IntersectPoint(*e, *eNext, Pt); + if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + SwapPositionsInSEL(e, eNext); isModified = true; } else e = eNext; } - if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0; + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; else break; } - m_SortedEdges = 0; + while ( isModified ); + m_SortedEdges = 0; //important } //------------------------------------------------------------------------------ -bool ProcessParam1BeforeParam2(IntersectNode &node1, IntersectNode &node2) -{ - bool result; - if (node1.pt.Y == node2.pt.Y) - { - if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) - { - result = node2.pt.X > node1.pt.X; - return node2.edge1->dx > 0 ? !result : result; - } - else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) - { - result = node2.pt.X > node1.pt.X; - return node2.edge2->dx > 0 ? !result : result; - } - else return node2.pt.X > node1.pt.X; - } - else return node1.pt.Y > node2.pt.Y; -} -//------------------------------------------------------------------------------ - -void Clipper::AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt) -{ - IntersectNode* newNode = new IntersectNode; - newNode->edge1 = e1; - newNode->edge2 = e2; - newNode->pt = pt; - newNode->next = 0; - if( !m_IntersectNodes ) m_IntersectNodes = newNode; - else if( ProcessParam1BeforeParam2(*newNode, *m_IntersectNodes) ) - { - newNode->next = m_IntersectNodes; - m_IntersectNodes = newNode; - } - else - { - IntersectNode* iNode = m_IntersectNodes; - while( iNode->next && ProcessParam1BeforeParam2(*iNode->next, *newNode) ) - iNode = iNode->next; - newNode->next = iNode->next; - iNode->next = newNode; - } -} -//------------------------------------------------------------------------------ void Clipper::ProcessIntersectList() { - while( m_IntersectNodes ) + for (size_t i = 0; i < m_IntersectList.size(); ++i) { - IntersectNode* iNode = m_IntersectNodes->next; + IntersectNode* iNode = m_IntersectList[i]; { - IntersectEdges( m_IntersectNodes->edge1 , - m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth ); - SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 ); + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); } - delete m_IntersectNodes; - m_IntersectNodes = iNode; + delete iNode; } + m_IntersectList.clear(); } //------------------------------------------------------------------------------ -void Clipper::DoMaxima(TEdge *e, long64 topY) +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) { - TEdge* eMaxPair = GetMaximaPair(e); - long64 X = e->xtop; - TEdge* eNext = e->nextInAEL; - while( eNext != eMaxPair ) + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) { - if (!eNext) throw clipperException("DoMaxima error"); - IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); - eNext = eNext->nextInAEL; + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); } - if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPairEx(e); + if (!eMaxPair) { - DeleteFromAEL( e ); - DeleteFromAEL( eMaxPair ); + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; } - else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 ) + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) { - IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone ); + IntersectEdges(e, eNext, e->Top); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif else throw clipperException("DoMaxima error"); } //------------------------------------------------------------------------------ -void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { TEdge* e = m_ActiveEdges; while( e ) { //1. process maxima, treating them as if they're 'bent' horizontal edges, // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) { - //'e' might be removed from AEL, as may any following edges so ... - TEdge* ePrior = e->prevInAEL; - DoMaxima(e, topY); - if( !ePrior ) e = m_ActiveEdges; - else e = ePrior->nextInAEL; + TEdge* eMaxPair = GetMaximaPairEx(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + if (m_StrictSimple) m_Maxima.push_back(e->Top.X); + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; } else { - //2. promote horizontal edges, otherwise update xcurr and ycurr ... - if( IsIntermediate(e, topY) && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) ) + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { - if (e->outIdx >= 0) - { - AddOutPt(e, IntPoint(e->xtop, e->ytop)); - - for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) - { - IntPoint pt, pt2; - HorzJoinRec* hj = m_HorizJoins[i]; - if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), - IntPoint(hj->edge->xtop, hj->edge->ytop), - IntPoint(e->nextInLML->xbot, e->nextInLML->ybot), - IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2)) - AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx); - } - - AddHorzJoin(e->nextInLML, e->outIdx); - } UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); AddEdgeToSEL(e); - } else + } + else { - //this just simplifies horizontal processing ... - e->xcurr = TopX( *e, topY ); - e->ycurr = topY; + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; +#ifdef use_xyz + e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0); +#endif + } + + //When StrictlySimple and 'e' is being touched by another edge, then + //make sure both edges have a vertex here ... + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt* op = AddOutPt(ePrev, pt); + OutPt* op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); //StrictlySimple (type-3) join + } } - e = e->nextInAEL; + + e = e->NextInAEL; } } - //3. Process horizontals at the top of the scanbeam ... + //3. Process horizontals at the Top of the scanbeam ... + m_Maxima.sort(); ProcessHorizontals(); + m_Maxima.clear(); //4. Promote intermediate vertices ... e = m_ActiveEdges; - while( e ) + while(e) { - if( IsIntermediate( e, topY ) ) + if(IsIntermediate(e, topY)) { - if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop)); + OutPt* op = 0; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); UpdateEdgeIntoAEL(e); //if output polygons share an edge, they'll need joining later ... - if (e->outIdx >= 0 && e->prevInAEL && e->prevInAEL->outIdx >= 0 && - e->prevInAEL->xcurr == e->xbot && e->prevInAEL->ycurr == e->ybot && - SlopesEqual(IntPoint(e->xbot,e->ybot), IntPoint(e->xtop, e->ytop), - IntPoint(e->xbot,e->ybot), - IntPoint(e->prevInAEL->xtop, e->prevInAEL->ytop), m_UseFullRange)) + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { - AddOutPt(e->prevInAEL, IntPoint(e->xbot, e->ybot)); - AddJoin(e, e->prevInAEL); + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); } - else if (e->outIdx >= 0 && e->nextInAEL && e->nextInAEL->outIdx >= 0 && - e->nextInAEL->ycurr > e->nextInAEL->ytop && - e->nextInAEL->ycurr <= e->nextInAEL->ybot && - e->nextInAEL->xcurr == e->xbot && e->nextInAEL->ycurr == e->ybot && - SlopesEqual(IntPoint(e->xbot,e->ybot), IntPoint(e->xtop, e->ytop), - IntPoint(e->xbot,e->ybot), - IntPoint(e->nextInAEL->xtop, e->nextInAEL->ytop), m_UseFullRange)) + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) { - AddOutPt(e->nextInAEL, IntPoint(e->xbot, e->ybot)); - AddJoin(e, e->nextInAEL); + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); } } - e = e->nextInAEL; + e = e->NextInAEL; } } //------------------------------------------------------------------------------ -void Clipper::FixupOutPolygon(OutRec &outRec) +void Clipper::FixupOutPolyline(OutRec &outrec) { - //FixupOutPolygon() - removes duplicate points and simplifies consecutive - //parallel edges by removing the middle vertex. - OutPt *lastOK = 0; - outRec.pts = outRec.bottomPt; - OutPt *pp = outRec.bottomPt; - - for (;;) + OutPt *pp = outrec.Pts; + OutPt *lastPP = pp->Prev; + while (pp != lastPP) { - if (pp->prev == pp || pp->prev == pp->next ) + pp = pp->Next; + if (pp->Pt == pp->Prev->Pt) { - DisposeOutPts(pp); - outRec.pts = 0; - outRec.bottomPt = 0; - return; - } - //test for duplicate points and for same slope (cross-product) ... - if ( PointsEqual(pp->pt, pp->next->pt) || - SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) ) - { - lastOK = 0; - OutPt *tmp = pp; - if (pp == outRec.bottomPt) - outRec.bottomPt = 0; //flags need for updating - pp->prev->next = pp->next; - pp->next->prev = pp->prev; - pp = pp->prev; - delete tmp; - } - else if (pp == lastOK) break; - else - { - if (!lastOK) lastOK = pp; - pp = pp->next; + if (pp == lastPP) lastPP = pp->Prev; + OutPt *tmpPP = pp->Prev; + tmpPP->Next = pp->Next; + pp->Next->Prev = tmpPP; + delete pp; + pp = tmpPP; } } - if (!outRec.bottomPt) { - outRec.bottomPt = GetBottomPt(pp); - outRec.bottomPt->idx = outRec.idx; - outRec.pts = outRec.bottomPt; + + if (pp == pp->Prev) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; } } //------------------------------------------------------------------------------ -void Clipper::BuildResult(Polygons &polys) +void Clipper::FixupOutPolygon(OutRec &outrec) { - int k = 0; - polys.resize(m_PolyOuts.size()); + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + bool preserveCol = m_PreserveCollinear || m_StrictSimple; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { - if (m_PolyOuts[i]->pts) + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int j = 0; j < cnt; ++j) { - Polygon* pg = &polys[k]; - pg->clear(); - OutPt* p = m_PolyOuts[i]->pts; - do - { - pg->push_back(p->pt); - p = p->next; - } while (p != m_PolyOuts[i]->pts); - //make sure each polygon has at least 3 vertices ... - if (pg->size() < 3) pg->clear(); else k++; + pg.push_back(p->Pt); + p = p->Prev; } + polys.push_back(pg); } - polys.resize(k); } //------------------------------------------------------------------------------ -void Clipper::BuildResultEx(ExPolygons &polys) +void Clipper::BuildResult2(PolyTree& polytree) { - PolyOutList::size_type i = 0; - int k = 0; - polys.resize(0); - polys.reserve(m_PolyOuts.size()); - while (i < m_PolyOuts.size() && m_PolyOuts[i]->pts) - { - ExPolygon epg; - OutPt* p = m_PolyOuts[i]->pts; - do { - epg.outer.push_back(p->pt); - p = p->next; - } while (p != m_PolyOuts[i]->pts); - i++; - //make sure polygons have at least 3 vertices ... - if (epg.outer.size() < 3) continue; - while (i < m_PolyOuts.size() - && m_PolyOuts[i]->pts && m_PolyOuts[i]->isHole) + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { - Polygon pg; - p = m_PolyOuts[i]->pts; - do { - pg.push_back(p->pt); - p = p->next; - } while (p != m_PolyOuts[i]->pts); - epg.holes.push_back(pg); - i++; + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); } - polys.push_back(epg); - k++; - } - polys.resize(k); } //------------------------------------------------------------------------------ void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) { - TEdge *e1 = int1.edge1; - TEdge *e2 = int1.edge2; - IntPoint p = int1.pt; - - int1.edge1 = int2.edge1; - int1.edge2 = int2.edge2; - int1.pt = int2.pt; - - int2.edge1 = e1; - int2.edge2 = e2; - int2.pt = p; + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; } //------------------------------------------------------------------------------ -bool Clipper::FixupIntersections() +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { - if ( !m_IntersectNodes->next ) return true; - - CopyAELToSEL(); - IntersectNode *int1 = m_IntersectNodes; - IntersectNode *int2 = m_IntersectNodes->next; - while (int2) + if (e2.Curr.X == e1.Curr.X) { - TEdge *e1 = int1->edge1; - TEdge *e2; - if (e1->prevInSEL == int1->edge2) e2 = e1->prevInSEL; - else if (e1->nextInSEL == int1->edge2) e2 = e1->nextInSEL; - else - { - //The current intersection is out of order, so try and swap it with - //a subsequent intersection ... - while (int2) - { - if (int2->edge1->nextInSEL == int2->edge2 || - int2->edge1->prevInSEL == int2->edge2) break; - else int2 = int2->next; - } - if ( !int2 ) return false; //oops!!! + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ - //found an intersect node that can be swapped ... - SwapIntersectNodes(*int1, *int2); - e1 = int1->edge1; - e2 = int1->edge2; - } - SwapPositionsInSEL(e1, e2); - int1 = int1->next; - int2 = int1->next; +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} } - - m_SortedEdges = 0; - - //finally, check the last intersection too ... - return (int1->edge1->prevInSEL == int1->edge2 || - int1->edge1->nextInSEL == int1->edge2); + return Left < Right; } //------------------------------------------------------------------------------ -bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) -{ - return e2.xcurr == e1.xcurr ? e2.dx > e1.dx : e2.xcurr < e1.xcurr; -} -//------------------------------------------------------------------------------ - -void Clipper::InsertEdgeIntoAEL(TEdge *edge) -{ - edge->prevInAEL = 0; - edge->nextInAEL = 0; - if( !m_ActiveEdges ) +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.Pts; + do { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; m_ActiveEdges = edge; } - else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) ) + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) { - edge->nextInAEL = m_ActiveEdges; - m_ActiveEdges->prevInAEL = edge; - m_ActiveEdges = edge; - } else + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else { - TEdge* e = m_ActiveEdges; - while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) ) - e = e->nextInAEL; - edge->nextInAEL = e->nextInAEL; - if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge; - edge->prevInAEL = e; - e->nextInAEL = edge; + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; } } //---------------------------------------------------------------------- -void Clipper::DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt) +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) { - AddOutPt(edge1, pt); - SwapSides(*edge1, *edge2); - SwapPolyIndexes(*edge1, *edge2); -} -//---------------------------------------------------------------------- - -void Clipper::DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt) -{ - AddOutPt(edge2, pt); - SwapSides(*edge1, *edge2); - SwapPolyIndexes(*edge1, *edge2); -} -//---------------------------------------------------------------------- - -void Clipper::DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt) -{ - AddOutPt(edge1, pt); - AddOutPt(edge2, pt); - SwapSides( *edge1 , *edge2 ); - SwapPolyIndexes( *edge1 , *edge2 ); -} -//---------------------------------------------------------------------- - -void Clipper::CheckHoleLinkages1(OutRec *outRec1, OutRec *outRec2) -{ - //when a polygon is split into 2 polygons, make sure any holes the original - //polygon contained link to the correct polygon ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) { - OutRec *orec = m_PolyOuts[i]; - if (orec->isHole && orec->bottomPt && orec->FirstLeft == outRec1 && - !PointInPolygon(orec->bottomPt->pt, outRec1->pts, m_UseFullRange)) - orec->FirstLeft = outRec2; - } -} -//---------------------------------------------------------------------- - -void Clipper::CheckHoleLinkages2(OutRec *outRec1, OutRec *outRec2) -{ - //if a hole is owned by outRec2 then make it owned by outRec1 ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - if (m_PolyOuts[i]->isHole && m_PolyOuts[i]->bottomPt && - m_PolyOuts[i]->FirstLeft == outRec2) - m_PolyOuts[i]->FirstLeft = outRec1; -} -//---------------------------------------------------------------------- - -void Clipper::JoinCommonEdges(bool fixHoleLinkages) -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else { - JoinRec* j = m_Joins[i]; - OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; - OutPt *pp1a = outRec1->pts; - OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; - OutPt *pp2a = outRec2->pts; - IntPoint pt1 = j->pt2a, pt2 = j->pt2b; - IntPoint pt3 = j->pt1a, pt4 = j->pt1b; - if (!FindSegment(pp1a, pt1, pt2)) continue; - if (j->poly1Idx == j->poly2Idx) - { - //we're searching the same polygon for overlapping segments so - //segment 2 mustn't be the same as segment 1 ... - pp2a = pp1a->next; - if (!FindSegment(pp2a, pt3, pt4) || (pp2a == pp1a)) continue; - } - else if (!FindSegment(pp2a, pt3, pt4)) continue; - - if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) continue; - - OutPt *p1, *p2, *p3, *p4; - OutPt *prev = pp1a->prev; - //get p1 & p2 polypts - the overlap start & endpoints on poly1 - if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; - else if (PointsEqual(prev->pt, pt1)) p1 = prev; - else p1 = InsertPolyPtBetween(pp1a, prev, pt1); - - if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; - else if (PointsEqual(prev->pt, pt2)) p2 = prev; - else if ((p1 == pp1a) || (p1 == prev)) - p2 = InsertPolyPtBetween(pp1a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) - p2 = InsertPolyPtBetween(pp1a, p1, pt2); else - p2 = InsertPolyPtBetween(p1, prev, pt2); - - //get p3 & p4 polypts - the overlap start & endpoints on poly2 - prev = pp2a->prev; - if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; - else if (PointsEqual(prev->pt, pt1)) p3 = prev; - else p3 = InsertPolyPtBetween(pp2a, prev, pt1); - - if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; - else if (PointsEqual(prev->pt, pt2)) p4 = prev; - else if ((p3 == pp2a) || (p3 == prev)) - p4 = InsertPolyPtBetween(pp2a, prev, pt2); - else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) - p4 = InsertPolyPtBetween(pp2a, p3, pt2); else - p4 = InsertPolyPtBetween(p3, prev, pt2); - - //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... - if (p1->next == p2 && p3->prev == p4) - { - p1->next = p3; - p3->prev = p1; - p2->prev = p4; - p4->next = p2; - } - else if (p1->prev == p2 && p3->next == p4) - { - p1->prev = p3; - p3->next = p1; - p2->next = p4; - p4->prev = p2; - } - else - continue; //an orientation is probably wrong - - if (j->poly2Idx == j->poly1Idx) - { - //instead of joining two polygons, we've just created a new one by - //splitting one polygon into two. - outRec1->pts = GetBottomPt(p1); - outRec1->bottomPt = outRec1->pts; - outRec1->bottomPt->idx = outRec1->idx; - outRec2 = CreateOutRec(); - m_PolyOuts.push_back(outRec2); - outRec2->idx = (int)m_PolyOuts.size()-1; - j->poly2Idx = outRec2->idx; - outRec2->pts = GetBottomPt(p2); - outRec2->bottomPt = outRec2->pts; - outRec2->bottomPt->idx = outRec2->idx; - - if (PointInPolygon(outRec2->pts->pt, outRec1->pts, m_UseFullRange)) - { - //outRec2 is contained by outRec1 ... - outRec2->isHole = !outRec1->isHole; - outRec2->FirstLeft = outRec1; - if (outRec2->isHole == - (m_ReverseOutput ^ Orientation(outRec2, m_UseFullRange))) - ReversePolyPtLinks(*outRec2->pts); - } else if (PointInPolygon(outRec1->pts->pt, outRec2->pts, m_UseFullRange)) - { - //outRec1 is contained by outRec2 ... - outRec2->isHole = outRec1->isHole; - outRec1->isHole = !outRec2->isHole; - outRec2->FirstLeft = outRec1->FirstLeft; - outRec1->FirstLeft = outRec2; - if (outRec1->isHole == - (m_ReverseOutput ^ Orientation(outRec1, m_UseFullRange))) - ReversePolyPtLinks(*outRec1->pts); - //make sure any contained holes now link to the correct polygon ... - if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); - } else - { - outRec2->isHole = outRec1->isHole; - outRec2->FirstLeft = outRec1->FirstLeft; - //make sure any contained holes now link to the correct polygon ... - if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); - } - - //now fixup any subsequent joins that match this polygon - for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) - { - JoinRec* j2 = m_Joins[k]; - if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, p2)) - j2->poly1Idx = j->poly2Idx; - if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, p2)) - j2->poly2Idx = j->poly2Idx; - } - - //now cleanup redundant edges too ... - FixupOutPolygon(*outRec1); - FixupOutPolygon(*outRec2); - - if (outRec1->pts && (Orientation(outRec1, m_UseFullRange) != (Area(*outRec1, m_UseFullRange) > 0))) - DisposeBottomPt(*outRec1); - if (outRec2->pts && (Orientation(outRec2, m_UseFullRange) != (Area(*outRec2, m_UseFullRange) > 0))) - DisposeBottomPt(*outRec2); - - } else - { - //joined 2 polygons together ... - - //make sure any holes contained by outRec2 now link to outRec1 ... - if (fixHoleLinkages) CheckHoleLinkages2(outRec1, outRec2); - - //now cleanup redundant edges too ... - FixupOutPolygon(*outRec1); - - if (outRec1->pts) - { - outRec1->isHole = !Orientation(outRec1, m_UseFullRange); - if (outRec1->isHole && !outRec1->FirstLeft) - outRec1->FirstLeft = outRec2->FirstLeft; - } - - //delete the obsolete pointer ... - int OKIdx = outRec1->idx; - int ObsoleteIdx = outRec2->idx; - outRec2->pts = 0; - outRec2->bottomPt = 0; - outRec2->AppendLink = outRec1; - - //now fixup any subsequent Joins that match this polygon - for (JoinList::size_type k = i+1; k < m_Joins.size(); k++) - { - JoinRec* j2 = m_Joins[k]; - if (j2->poly1Idx == ObsoleteIdx) j2->poly1Idx = OKIdx; - if (j2->poly2Idx == ObsoleteIdx) j2->poly2Idx = OKIdx; - } - } - } -} -//------------------------------------------------------------------------------ - -void ReversePolygon(Polygon& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePolygons(Polygons& p) -{ - for (Polygons::size_type i = 0; i < p.size(); ++i) - ReversePolygon(p[i]); -} - -//------------------------------------------------------------------------------ -// OffsetPolygon functions ... -//------------------------------------------------------------------------------ - -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} -}; -//------------------------------------------------------------------------------ - -Polygon BuildArc(const IntPoint &pt, - const double a1, const double a2, const double r) -{ - long64 steps = std::max(6, int(std::sqrt(std::fabs(r)) * std::fabs(a2 - a1))); - if (steps > 0x100000) steps = 0x100000; - int n = (unsigned)steps; - Polygon result(n); - double da = (a2 - a1) / (n -1); - double a = a1; - for (int i = 0; i < n; ++i) - { - result[i].X = pt.X + Round(std::cos(a)*r); - result[i].Y = pt.Y + Round(std::sin(a)*r); - a += da; + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; } return result; } //------------------------------------------------------------------------------ -DoublePoint GetUnitNormal( const IntPoint &pt1, const IntPoint &pt2) +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) +{ + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + if (outRec1 != outRec2) return false; + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + //tests if NewOutRec contains the polygon before reassigning FirstLeft + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) +{ + //A polygon has split into two such that one is now the inner of the other. + //It's possible that these polygons now wrap around other polygons, so check + //every polygon that's also contained by OuterOutRec's FirstLeft container + //(including 0) to see if they've become inner to the new inner polygon ... + OutRec* orfl = OuterOutRec->FirstLeft; + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + + if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) + continue; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) + continue; + if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) + outRec->FirstLeft = InnerOutRec; + else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) + outRec->FirstLeft = OuterOutRec; + else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) + outRec->FirstLeft = orfl; + } +} +//---------------------------------------------------------------------- +void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) +{ + //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + Join* join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + if (outRec1->IsOpen || outRec2->IsOpen) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) + { + //outRec1 contains outRec2 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) + { + //outRec2 contains outRec1 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); - double dx = (double)(pt2.X - pt1.X); + double Dx = (double)(pt2.X - pt1.X); double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( dx*dx + dy*dy ); - dx *= f; + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; dy *= f; - return DoublePoint(dy, -dx); + return DoublePoint(dy, -Dx); } //------------------------------------------------------------------------------ +// ClipperOffset class //------------------------------------------------------------------------------ -class PolyOffsetBuilder +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) { -private: - Polygons m_p; - Polygon* m_curr_poly; - std::vector normals; - double m_delta, m_RMin, m_R; - size_t m_i, m_j, m_k; - static const int buffLength = 128; - JoinType m_jointype; - -public: - -PolyOffsetBuilder(const Polygons& in_polys, Polygons& out_polys, - double delta, JoinType jointype, double MiterLimit) -{ - //nb precondition - out_polys != ptsin_polys - if (NEAR_ZERO(delta)) - { - out_polys = in_polys; - return; - } - - this->m_p = in_polys; - this->m_delta = delta; - this->m_jointype = jointype; - if (MiterLimit <= 1) MiterLimit = 1; - m_RMin = 2/(MiterLimit*MiterLimit); - - double deltaSq = delta*delta; - out_polys.clear(); - out_polys.resize(in_polys.size()); - for (m_i = 0; m_i < in_polys.size(); m_i++) - { - m_curr_poly = &out_polys[m_i]; - size_t len = in_polys[m_i].size(); - if (len > 1 && m_p[m_i][0].X == m_p[m_i][len - 1].X && - m_p[m_i][0].Y == m_p[m_i][len-1].Y) len--; - - //when 'shrinking' polygons - to minimize artefacts - //strip those polygons that have an area < pi * delta^2 ... - double a1 = Area(in_polys[m_i]); - if (delta < 0) { if (a1 > 0 && a1 < deltaSq *pi) len = 0; } - else if (a1 < 0 && -a1 < deltaSq *pi) len = 0; //holes have neg. area - - if (len == 0 || (len < 3 && delta <= 0)) - continue; - else if (len == 1) - { - Polygon arc; - arc = BuildArc(in_polys[m_i][len-1], 0, 2 * pi, delta); - out_polys[m_i] = arc; - continue; - } - - //build normals ... - normals.clear(); - normals.resize(len); - normals[len-1] = GetUnitNormal(in_polys[m_i][len-1], in_polys[m_i][0]); - for (m_j = 0; m_j < len -1; ++m_j) - normals[m_j] = GetUnitNormal(in_polys[m_i][m_j], in_polys[m_i][m_j+1]); - - m_k = len -1; - for (m_j = 0; m_j < len; ++m_j) - { - switch (jointype) - { - case jtMiter: - { - m_R = 1 + (normals[m_j].X*normals[m_k].X + - normals[m_j].Y*normals[m_k].Y); - if (m_R >= m_RMin) DoMiter(); else DoSquare(MiterLimit); - break; - } - case jtSquare: DoSquare(); break; - case jtRound: DoRound(); break; - } - m_k = m_j; - } - } - - //finally, clean up untidy corners using Clipper ... - Clipper clpr; - clpr.AddPolygons(out_polys, ptSubject); - if (delta > 0) - { - if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) - out_polys.clear(); - } - else - { - IntRect r = clpr.GetBounds(); - Polygon outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPolygon(outer, ptSubject); - if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) - { - out_polys.erase(out_polys.begin()); - ReversePolygons(out_polys); - - } else - out_polys.clear(); - } + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; } //------------------------------------------------------------------------------ -private: - -void AddPoint(const IntPoint& pt) +ClipperOffset::~ClipperOffset() { - Polygon::size_type len = m_curr_poly->size(); - if (len == m_curr_poly->capacity()) - m_curr_poly->reserve(len + buffLength); - m_curr_poly->push_back(pt); + Clear(); } //------------------------------------------------------------------------------ -void DoSquare(double mul = 1.0) +void ClipperOffset::Clear() { - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) - { - double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); - double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); - a1 = std::fabs(a2 - a1); - if (a1 > pi) a1 = pi * 2 - a1; - double dx = std::tan((pi - a1)/4) * std::fabs(m_delta * mul); - pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), - (long64)(pt1.Y + normals[m_k].X * dx)); - AddPoint(pt1); - pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), - (long64)(pt2.Y -normals[m_j].X * dx)); - AddPoint(pt2); - } - else - { - AddPoint(pt1); - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); - } + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; } //------------------------------------------------------------------------------ -void DoMiter() +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) { - if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) { - double q = m_delta / m_R; - AddPoint(IntPoint((long64)Round(m_p[m_i][m_j].X + - (normals[m_k].X + normals[m_j].X) * q), - (long64)Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; } - else - { - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * - m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * - m_delta), (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); - } -} -//------------------------------------------------------------------------------ - -void DoRound() -{ - IntPoint pt1 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); - IntPoint pt2 = IntPoint((long64)Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - (long64)Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg). - if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0) - { - if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985) - { - double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); - double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); - if (m_delta > 0 && a2 < a1) a2 += pi *2; - else if (m_delta < 0 && a2 > a1) a2 -= pi *2; - Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta); - for (Polygon::size_type m = 0; m < arc.size(); m++) - AddPoint(arc[m]); - } - } - else - AddPoint(m_p[m_i][m_j]); - AddPoint(pt2); -} -//-------------------------------------------------------------------------- - -}; //end PolyOffsetBuilder - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype, double MiterLimit) -{ - if (&out_polys == &in_polys) + if (endType == etClosedPolygon && j < 2) { - Polygons poly2(in_polys); - PolyOffsetBuilder(poly2, out_polys, delta, jointype, MiterLimit); + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); } - else PolyOffsetBuilder(in_polys, out_polys, delta, jointype, MiterLimit); } //------------------------------------------------------------------------------ -void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType) +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + solution.Childs[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + //cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (std::fabs(m_sinA * m_delta) < 1.0) + { + //dot product ... + double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + //else angle => 180 degrees + } + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op || outrec->IsOpen) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + } + else + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + } + else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) { Clipper c; - c.AddPolygon(in_poly, ptSubject); + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ -void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType) +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) { Clipper c; - c.AddPolygons(in_polys, ptSubject); + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ -void SimplifyPolygons(Polygons &polys, PolyFillType fillType) +void SimplifyPolygons(Paths &polys, PolyFillType fillType) { SimplifyPolygons(polys, polys, fillType); } //------------------------------------------------------------------------------ -std::ostream& operator <<(std::ostream &s, IntPoint& p) +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { - s << p.X << ' ' << p.Y << "\n"; + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) +{ + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x_1, y_1) & (x_2, y_2) is ... + //(y_1 - y_2)x + (x_2 - x_1)y - (y_1 - y_2)x_1 - (x_2 - x_1)y_1 = 0 + //A = (y_1 - y_2); B = (x_2 - x_1); C = - (y_1 - y_2)x_1 - (x_2 - x_1)y_1 + //perpendicular distance of point (x_0, y_0) = |Ax_0 + By_0 + C| / Sqrt(A^2 + B^2) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + //this function is more accurate when the point that's geometrically + //between the other 2 points is the one that's tested for distance. + //ie makes it more likely to pick up 'spikes' ... + if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) + { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + else + { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) + { + out_poly.clear(); + return; + } + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + out_polys.resize(in_polys.size()); + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) + { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + solution.push_back(quad); + } +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) +{ + Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void TranslatePath(const Path& input, Path& output, const IntPoint delta) +{ + //precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) +{ + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) + { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) + { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } + } + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) +{ + Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.push_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; return s; } //------------------------------------------------------------------------------ -std::ostream& operator <<(std::ostream &s, Polygon &p) +std::ostream& operator <<(std::ostream &s, const Path &p) { - for (Polygon::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; return s; } //------------------------------------------------------------------------------ -std::ostream& operator <<(std::ostream &s, Polygons &p) +std::ostream& operator <<(std::ostream &s, const Paths &p) { - for (Polygons::size_type i = 0; i < p.size(); i++) + for (Paths::size_type i = 0; i < p.size(); i++) s << p[i]; s << "\n"; return s; diff --git a/Engine/lib/assimp/contrib/clipper/clipper.hpp b/Engine/lib/assimp/contrib/clipper/clipper.hpp index 7cdac6c3f..5a19617bb 100644 --- a/Engine/lib/assimp/contrib/clipper/clipper.hpp +++ b/Engine/lib/assimp/contrib/clipper/clipper.hpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 4.8.8 * -* Date : 30 August 2012 * +* Version : 6.4.2 * +* Date : 27 February 2017 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2012 * +* Copyright : Angus Johnson 2010-2017 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -34,11 +34,30 @@ #ifndef clipper_hpp #define clipper_hpp +#define CLIPPER_VERSION "6.4.2" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +#define use_lines + +//use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + #include +#include +#include #include #include #include #include +#include +#include namespace ClipperLib { @@ -50,129 +69,150 @@ enum PolyType { ptSubject, ptClip }; //see http://glprogramming.com/red/chapter11.html enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; -typedef signed long long long64; -typedef unsigned long long ulong64; +#ifdef use_int32 + typedef int cInt; + static cInt const loRange = 0x7FFF; + static cInt const hiRange = 0x7FFF; +#else + typedef signed long long cInt; + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef signed long long long64; //used by Int128 class + typedef unsigned long long ulong64; + +#endif struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +std::ostream& operator <<(std::ostream &s, const IntPoint &p); +std::ostream& operator <<(std::ostream &s, const Path &p); +std::ostream& operator <<(std::ostream &s, const Paths &p); + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); +#endif + +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ public: - long64 X; - long64 Y; - IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {}; - friend std::ostream& operator <<(std::ostream &s, IntPoint &p); + PolyNode(); + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + //PolyNode& operator =(PolyNode& other); + unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; + void AddChild(PolyNode& child); + friend class Clipper; //to access Index + friend class ClipperOffset; }; -typedef std::vector< IntPoint > Polygon; -typedef std::vector< Polygon > Polygons; - -std::ostream& operator <<(std::ostream &s, Polygon &p); -std::ostream& operator <<(std::ostream &s, Polygons &p); - -struct ExPolygon { - Polygon outer; - Polygons holes; -}; -typedef std::vector< ExPolygon > ExPolygons; - -enum JoinType { jtSquare, jtRound, jtMiter }; - -bool Orientation(const Polygon &poly); -double Area(const Polygon &poly); -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype = jtSquare, double MiterLimit = 2); -void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); - -void ReversePolygon(Polygon& p); -void ReversePolygons(Polygons& p); - -//used internally ... -enum EdgeSide { esNeither = 0, esLeft = 1, esRight = 2, esBoth = 3 }; -enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; - -struct TEdge { - long64 xbot; - long64 ybot; - long64 xcurr; - long64 ycurr; - long64 xtop; - long64 ytop; - double dx; - long64 tmpX; - PolyType polyType; - EdgeSide side; - int windDelta; //1 or -1 depending on winding direction - int windCnt; - int windCnt2; //winding count of the opposite polytype - int outIdx; - TEdge *next; - TEdge *prev; - TEdge *nextInLML; - TEdge *nextInAEL; - TEdge *prevInAEL; - TEdge *nextInSEL; - TEdge *prevInSEL; +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){ Clear(); }; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + //PolyTree& operator =(PolyTree& other); + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes }; -struct IntersectNode { - TEdge *edge1; - TEdge *edge2; - IntPoint pt; - IntersectNode *next; -}; +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); -struct LocalMinima { - long64 Y; - TEdge *leftBound; - TEdge *rightBound; - LocalMinima *next; -}; +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); -struct Scanbeam { - long64 Y; - Scanbeam *next; -}; +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +void CleanPolygon(Path& poly, double distance = 1.415); +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); +void CleanPolygons(Paths& polys, double distance = 1.415); -struct OutPt; //forward declaration +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); -struct OutRec { - int idx; - bool isHole; - OutRec *FirstLeft; - OutRec *AppendLink; - OutPt *pts; - OutPt *bottomPt; - OutPt *bottomFlag; - EdgeSide sides; -}; +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); -struct OutPt { - int idx; - IntPoint pt; - OutPt *next; - OutPt *prev; -}; +void ReversePath(Path& p); +void ReversePaths(Paths& p); -struct JoinRec { - IntPoint pt1a; - IntPoint pt1b; - int poly1Idx; - IntPoint pt2a; - IntPoint pt2b; - int poly2Idx; -}; +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; -struct HorzJoinRec { - TEdge *edge; - int savedIdx; -}; +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; -struct IntRect { long64 left; long64 top; long64 right; long64 bottom; }; +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct OutPt; +struct OutRec; +struct Join; typedef std::vector < OutRec* > PolyOutList; typedef std::vector < TEdge* > EdgeList; -typedef std::vector < JoinRec* > JoinList; -typedef std::vector < HorzJoinRec* > HorzJoinList; +typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + +//------------------------------------------------------------------------------ //ClipperBase is the ancestor to the Clipper class. It should not be //instantiated directly. This class simply abstracts the conversion of sets of @@ -182,110 +222,170 @@ class ClipperBase public: ClipperBase(); virtual ~ClipperBase(); - bool AddPolygon(const Polygon &pg, PolyType polyType); - bool AddPolygons( const Polygons &ppg, PolyType polyType); + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); virtual void Clear(); IntRect GetBounds(); + bool PreserveCollinear() {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; protected: void DisposeLocalMinimaList(); - TEdge* AddBoundsToLML(TEdge *e); - void PopLocalMinima(); + TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); virtual void Reset(); - void InsertLocalMinima(LocalMinima *newLm); - LocalMinima *m_CurrentLM; - LocalMinima *m_MinimaList; + TEdge* ProcessBound(TEdge* E, bool IsClockwise); + void InsertScanbeam(const cInt Y); + bool PopScanbeam(cInt &Y); + bool LocalMinimaPending(); + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + OutRec* CreateOutRec(); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + bool m_UseFullRange; EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; }; +//------------------------------------------------------------------------------ class Clipper : public virtual ClipperBase { public: - Clipper(); - ~Clipper(); + Clipper(int initOptions = 0); bool Execute(ClipType clipType, - Polygons &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); + Paths &solution, + PolyFillType fillType = pftEvenOdd); bool Execute(ClipType clipType, - ExPolygons &solution, - PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); - void Clear(); - bool ReverseSolution() {return m_ReverseOutput;}; + Paths &solution, + PolyFillType subjFillType, + PolyFillType clipFillType); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType fillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType, + PolyFillType clipFillType); + bool ReverseSolution() { return m_ReverseOutput; }; void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif protected: - void Reset(); - virtual bool ExecuteInternal(bool fixHoleLinkages); + virtual bool ExecuteInternal(); private: - PolyOutList m_PolyOuts; - JoinList m_Joins; - HorzJoinList m_HorizJoins; - ClipType m_ClipType; - Scanbeam *m_Scanbeam; - TEdge *m_ActiveEdges; + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::list MaximaList; + MaximaList m_Maxima; TEdge *m_SortedEdges; - IntersectNode *m_IntersectNodes; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - void DisposeScanbeamList(); + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; //custom callback +#endif void SetWindingCount(TEdge& edge); bool IsEvenOddFillType(const TEdge& edge) const; bool IsEvenOddAltFillType(const TEdge& edge) const; - void InsertScanbeam(const long64 Y); - long64 PopScanbeam(); - void InsertLocalMinimaIntoAEL(const long64 botY); - void InsertEdgeIntoAEL(TEdge *edge); + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); void AddEdgeToSEL(TEdge *edge); + bool PopEdgeFromSEL(TEdge *&edge); void CopyAELToSEL(); void DeleteFromSEL(TEdge *e); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); bool IsContributing(const TEdge& edge) const; - bool IsTopHorz(const long64 XPos); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - void DoMaxima(TEdge *e, long64 topY); + bool IsTopHorz(const cInt XPos); + void DoMaxima(TEdge *e); void ProcessHorizontals(); void ProcessHorizontal(TEdge *horzEdge); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); void AppendPolygon(TEdge *e1, TEdge *e2); - void DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt); - void IntersectEdges(TEdge *e1, TEdge *e2, - const IntPoint &pt, IntersectProtects protects); - OutRec* CreateOutRec(); - void AddOutPt(TEdge *e, const IntPoint &pt); - void DisposeBottomPt(OutRec &outRec); - void DisposeAllPolyPts(); - void DisposeOutRec(PolyOutList::size_type index); - bool ProcessIntersections(const long64 botY, const long64 topY); - void AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); - void BuildIntersectList(const long64 botY, const long64 topY); + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + OutPt* GetLastOutPt(TEdge *e); + bool ProcessIntersections(const cInt topY); + void BuildIntersectList(const cInt topY); void ProcessIntersectList(); - void ProcessEdgesAtTopOfScanbeam(const long64 topY); - void BuildResult(Polygons& polys); - void BuildResultEx(ExPolygons& polys); - void SetHoleState(TEdge *e, OutRec *OutRec); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); void DisposeIntersectNodes(); - bool FixupIntersections(); - void FixupOutPolygon(OutRec &outRec); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + void FixupOutPolyline(OutRec &outrec); bool IsHole(TEdge *e); - void FixHoleLinkage(OutRec *outRec); - void CheckHoleLinkages1(OutRec *outRec1, OutRec *outRec2); - void CheckHoleLinkages2(OutRec *outRec1, OutRec *outRec2); - void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); void ClearJoins(); - void AddHorzJoin(TEdge *e, int idx); - void ClearHorzJoins(); - void JoinCommonEdges(bool fixHoleLinkages); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint offPt); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec); + void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); +#endif }; - //------------------------------------------------------------------------------ + +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; //------------------------------------------------------------------------------ class clipperException : public std::exception diff --git a/Engine/lib/assimp/contrib/draco/.cmake-format.py b/Engine/lib/assimp/contrib/draco/.cmake-format.py index 64f2495b4..5b36f67aa 100644 --- a/Engine/lib/assimp/contrib/draco/.cmake-format.py +++ b/Engine/lib/assimp/contrib/draco/.cmake-format.py @@ -1,102 +1,137 @@ -# Generated with cmake-format 0.5.1 -# How wide to allow formatted cmake files -line_width = 80 - -# How many spaces to tab for indent -tab_size = 2 - -# If arglists are longer than this, break them always -max_subargs_per_line = 10 - -# If true, separate flow control names from their parentheses with a space -separate_ctrl_name_with_space = False - -# If true, separate function names from parentheses with a space -separate_fn_name_with_space = False - -# If a statement is wrapped to more than one line, than dangle the closing -# parenthesis on its own line -dangle_parens = False - -# What character to use for bulleted lists -bullet_char = '*' - -# What character to use as punctuation after numerals in an enumerated list -enum_char = '.' - -# What style line endings to use in the output. -line_ending = u'unix' - -# Format command names consistently as 'lower' or 'upper' case -command_case = u'lower' - -# Format keywords consistently as 'lower' or 'upper' case -keyword_case = u'unchanged' - -# Specify structure for custom cmake functions -additional_commands = { - "foo": { - "flags": [ - "BAR", - "BAZ" - ], - "kwargs": { - "HEADERS": "*", - "DEPENDS": "*", - "SOURCES": "*" - } +with section('parse'): + # Specify structure for custom cmake functions + additional_commands = { + 'draco_add_emscripten_executable': { + 'kwargs': { + 'NAME': '*', + 'SOURCES': '*', + 'OUTPUT_NAME': '*', + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + 'GLUE_PATH': '*', + 'PRE_LINK_JS_SOURCES': '*', + 'POST_LINK_JS_SOURCES': '*', + 'FEATURES': '*', + }, + 'pargs': 0, + }, + 'draco_add_executable': { + 'kwargs': { + 'NAME': '*', + 'SOURCES': '*', + 'OUTPUT_NAME': '*', + 'TEST': 0, + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + }, + 'pargs': 0, + }, + 'draco_add_library': { + 'kwargs': { + 'NAME': '*', + 'TYPE': '*', + 'SOURCES': '*', + 'TEST': 0, + 'OUTPUT_NAME': '*', + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + 'PUBLIC_INCLUDES': '*', + }, + 'pargs': 0, + }, + 'draco_generate_emscripten_glue': { + 'kwargs': { + 'INPUT_IDL': '*', + 'OUTPUT_PATH': '*', + }, + 'pargs': 0, + }, + 'draco_get_required_emscripten_flags': { + 'kwargs': { + 'FLAG_LIST_VAR_COMPILER': '*', + 'FLAG_LIST_VAR_LINKER': '*', + }, + 'pargs': 0, + }, + 'draco_option': { + 'kwargs': { + 'NAME': '*', + 'HELPSTRING': '*', + 'VALUE': '*', + }, + 'pargs': 0, + }, + # Rules for built in CMake commands and those from dependencies. + 'list': { + 'kwargs': { + 'APPEND': '*', + 'FILTER': '*', + 'FIND': '*', + 'GET': '*', + 'INSERT': '*', + 'JOIN': '*', + 'LENGTH': '*', + 'POP_BACK': '*', + 'POP_FRONT': '*', + 'PREPEND': '*', + 'REMOVE_DUPLICATES': '*', + 'REMOVE_ITEM': '*', + 'REVERSE': '*', + 'SORT': '*', + 'SUBLIST': '*', + 'TRANSFORM': '*', + }, + }, + 'protobuf_generate': { + 'kwargs': { + 'IMPORT_DIRS': '*', + 'LANGUAGE': '*', + 'OUT_VAR': '*', + 'PROTOC_OUT_DIR': '*', + 'PROTOS': '*', + }, + }, } -} -# A list of command names which should always be wrapped -always_wrap = [] +with section('format'): + # Formatting options. -# Specify the order of wrapping algorithms during successive reflow attempts -algorithm_order = [0, 1, 2, 3, 4] + # How wide to allow formatted cmake files + line_width = 80 -# If true, the argument lists which are known to be sortable will be sorted -# lexicographicall -autosort = False + # How many spaces to tab for indent + tab_size = 2 -# enable comment markup parsing and reflow -enable_markup = True + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False -# If comment markup is enabled, don't reflow the first comment block in -# eachlistfile. Use this to preserve formatting of your -# copyright/licensestatements. -first_comment_is_literal = False + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False -# If comment markup is enabled, don't reflow any comment block which matchesthis -# (regex) pattern. Default is `None` (disabled). -literal_comment_pattern = None + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = False -# Regular expression to match preformat fences in comments -# default=r'^\s*([`~]{3}[`~]*)(.*)$' -fence_pattern = u'^\\s*([`~]{3}[`~]*)(.*)$' + # Do not sort argument lists. + enable_sort = False -# Regular expression to match rulers in comments -# default=r'^\s*[^\w\s]{3}.*[^\w\s]{3}$' -ruler_pattern = u'^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' + # What style line endings to use in the output. + line_ending = 'unix' -# If true, emit the unicode byte-order mark (BOM) at the start of the file -emit_byteorder_mark = False + # Format command names consistently as 'lower' or 'upper' case + command_case = 'canonical' -# If a comment line starts with at least this many consecutive hash characters, -# then don't lstrip() them off. This allows for lazy hash rulers where the first -# hash char is not separated by space -hashruler_min_length = 10 - -# If true, then insert a space between the first hash char and remaining hash -# chars in a hash ruler, and normalize its length to fill the column -canonicalize_hashrulers = True - -# Specify the encoding of the input file. Defaults to utf-8. -input_encoding = u'utf-8' - -# Specify the encoding of the output file. Defaults to utf-8. Note that cmake -# only claims to support utf-8 so be careful when using anything else -output_encoding = u'utf-8' - -# A dictionary containing any per-command configuration overrides. Currently -# only `command_case` is supported. -per_command = {} + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = 'upper' diff --git a/Engine/lib/assimp/contrib/draco/BUILDING.md b/Engine/lib/assimp/contrib/draco/BUILDING.md index d33917b88..8e1f13e1f 100644 --- a/Engine/lib/assimp/contrib/draco/BUILDING.md +++ b/Engine/lib/assimp/contrib/draco/BUILDING.md @@ -1,19 +1,23 @@ _**Contents**_ - * [CMake Basics](#cmake-basics) - * [Mac OS X](#mac-os-x) - * [Windows](#windows) - * [CMake Build Configuration](#cmake-build-configuration) - * [Debugging and Optimization](#debugging-and-optimization) - * [Googletest Integration](#googletest-integration) - * [Javascript Encoder/Decoder](#javascript-encoderdecoder) - * [WebAssembly Decoder](#webassembly-decoder) - * [WebAssembly Mesh Only Decoder](#webassembly-mesh-only-decoder) - * [WebAssembly Point Cloud Only Decoder](#webassembly-point-cloud-only-decoder) - * [iOS Builds](#ios-builds) - * [Android Studio Project Integration](#android-studio-project-integration) - * [Native Android Builds](#native-android-builds) - * [vcpkg](#vcpkg) +- [Building](#building) + - [CMake Basics](#cmake-basics) + - [Mac OS X](#mac-os-x) + - [Windows](#windows) + - [CMake Build Configuration](#cmake-build-configuration) + - [Transcoder](#transcoder) + - [Debugging and Optimization](#debugging-and-optimization) + - [Googletest Integration](#googletest-integration) + - [Third Party Libraries](#third-party-libraries) + - [WebAssembly Decoder](#webassembly-decoder) + - [WebAssembly Mesh Only Decoder](#webassembly-mesh-only-decoder) + - [WebAssembly Point Cloud Only Decoder](#webassembly-point-cloud-only-decoder) + - [Javascript Encoder/Decoder](#javascript-encoderdecoder) + - [iOS Builds](#ios-builds) + - [Native Android Builds](#native-android-builds) + - [Android Studio Project Integration](#android-studio-project-integration) + - [Draco - Static Library](#draco---static-library) + - [vcpkg](#vcpkg) Building ======== @@ -72,6 +76,43 @@ C:\Users\nobody> cmake ../ -G "Visual Studio 16 2019" -A x64 CMake Build Configuration ------------------------- +Transcoder +---------- + +Before attempting to build Draco with transcoding support you must run an +additional Git command to obtain the submodules: + +~~~~~ bash +# Run this command from within your Draco clone. +$ git submodule update --init +# See below if you prefer to use existing versions of Draco dependencies. +~~~~~ + +In order to build the `draco_transcoder` target, the transcoding support needs +to be explicitly enabled when you run `cmake`, for example: + +~~~~~ bash +$ cmake ../ -DDRACO_TRANSCODER_SUPPORTED=ON +~~~~~ + +The above option is currently not compatible with our Javascript or WebAssembly +builds but all other use cases are supported. Note that binaries and libraries +built with the transcoder support may result in increased binary sizes of the +produced libraries and executables compared to the default CMake settings. + +The following CMake variables can be used to configure Draco to use local +copies of third party dependencies instead of git submodules. + +- `DRACO_EIGEN_PATH`: this path must contain an Eigen directory that includes + the Eigen sources. +- `DRACO_FILESYSTEM_PATH`: this path must contain the ghc directory where the + filesystem includes are located. +- `DRACO_TINYGLTF_PATH`: this path must contain tiny_gltf.h and its + dependencies. + +When not specified the Draco build requires the presence of the submodules that +are stored within `draco/third_party`. + Debugging and Optimization -------------------------- @@ -114,17 +155,52 @@ $ cmake ../ -DDRACO_SANITIZE=address Googletest Integration ---------------------- -Draco includes testing support built using Googletest. To enable Googletest unit -test support the DRACO_TESTS cmake variable must be turned on at cmake -generation time: +Draco includes testing support built using Googletest. The Googletest repository +is included as a submodule of the Draco git repository. Run the following +command to clone the Googletest repository: + +~~~~~ bash +$ git submodule update --init +~~~~~ + +To enable Googletest unit test support the DRACO_TESTS cmake variable must be +turned on at cmake generation time: ~~~~~ bash $ cmake ../ -DDRACO_TESTS=ON ~~~~~ -When cmake is used as shown in the above example the googletest directory must -be a sibling of the Draco repository root directory. To run the tests execute -`draco_tests` from your build output directory. +To run the tests execute `draco_tests` from your build output directory: + +~~~~~ bash +$ ./draco_tests +~~~~~ + +Draco can be configured to use a local Googletest installation. The +`DRACO_GOOGLETEST_PATH` variable overrides the behavior described above and +configures Draco to use the Googletest at the specified path. + +Third Party Libraries +--------------------- + +When Draco is built with transcoding and/or testing support enabled the project +has dependencies on third party libraries: + +- [Eigen](https://eigen.tuxfamily.org/) + - Provides various math utilites. +- [Googletest](https://github.com/google/googletest) + - Provides testing support. +- [Gulrak/filesystem](https://github.com/gulrak/filesystem) + - Provides C++17 std::filesystem emulation for pre-C++17 environments. +- [TinyGLTF](https://github.com/syoyo/tinygltf) + - Provides GLTF I/O support. + +These dependencies are managed as Git submodules. To obtain the dependencies +run the following command in your Draco repository: + +~~~~~ bash +$ git submodule update --init +~~~~~ WebAssembly Decoder ------------------- @@ -251,7 +327,7 @@ Draco - Static Library To include Draco in an existing or new Android Studio project, reference it from the `cmake` file of an existing native project that has a minimum SDK -version of 18 or higher. The project must support C++11. +version of 18 or higher. The project must support C++11 at least. To add Draco to your project: 1. Create a new "Native C++" project. diff --git a/Engine/lib/assimp/contrib/draco/CMakeLists.txt b/Engine/lib/assimp/contrib/draco/CMakeLists.txt index 6ea9b21fd..deba97931 100644 --- a/Engine/lib/assimp/contrib/draco/CMakeLists.txt +++ b/Engine/lib/assimp/contrib/draco/CMakeLists.txt @@ -1,7 +1,18 @@ -cmake_minimum_required(VERSION 3.12 FATAL_ERROR) +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. -# Draco requires C++11. -set(CMAKE_CXX_STANDARD 11) +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) project(draco C CXX) if(NOT CMAKE_BUILD_TYPE) @@ -14,17 +25,19 @@ set(draco_build "${Assimp_BINARY_DIR}") if("${draco_root}" STREQUAL "${draco_build}") message( - FATAL_ERROR "Building from within the Draco source tree is not supported.\n" - "Hint: Run these commands\n" - "$ rm -rf CMakeCache.txt CMakeFiles\n" - "$ mkdir -p ../draco_build\n" "$ cd ../draco_build\n" - "And re-run CMake from the draco_build directory.") + FATAL_ERROR + "Building from within the Draco source tree is not supported.\n" + "Hint: Run these commands\n" + "$ rm -rf CMakeCache.txt CMakeFiles\n" + "$ mkdir -p ../draco_build\n" + "$ cd ../draco_build\n" + "And re-run CMake from the draco_build directory.") endif() -include(CMakePackageConfigHelpers) include(FindPythonInterp) include("${draco_root}/cmake/draco_build_definitions.cmake") include("${draco_root}/cmake/draco_cpu_detection.cmake") +include("${draco_root}/cmake/draco_dependencies.cmake") include("${draco_root}/cmake/draco_emscripten.cmake") include("${draco_root}/cmake/draco_flags.cmake") include("${draco_root}/cmake/draco_helpers.cmake") @@ -49,6 +62,7 @@ draco_track_configuration_variable(DRACO_GENERATED_SOURCES_DIRECTORY) # Controls use of std::mutex and absl::Mutex in ThreadPool. draco_track_configuration_variable(DRACO_THREADPOOL_USE_STD_MUTEX) + if(DRACO_VERBOSE) draco_dump_cmake_flag_variables() draco_dump_tracked_configuration_variables() @@ -68,29 +82,32 @@ draco_reset_target_lists() draco_setup_options() draco_set_build_definitions() draco_set_cxx_flags() +draco_set_exe_linker_flags() draco_generate_features_h() # Draco source file listing variables. -list(APPEND draco_attributes_sources - "${draco_src_root}/attributes/attribute_octahedron_transform.cc" - "${draco_src_root}/attributes/attribute_octahedron_transform.h" - "${draco_src_root}/attributes/attribute_quantization_transform.cc" - "${draco_src_root}/attributes/attribute_quantization_transform.h" - "${draco_src_root}/attributes/attribute_transform.cc" - "${draco_src_root}/attributes/attribute_transform.h" - "${draco_src_root}/attributes/attribute_transform_data.h" - "${draco_src_root}/attributes/attribute_transform_type.h" - "${draco_src_root}/attributes/geometry_attribute.cc" - "${draco_src_root}/attributes/geometry_attribute.h" - "${draco_src_root}/attributes/geometry_indices.h" - "${draco_src_root}/attributes/point_attribute.cc" - "${draco_src_root}/attributes/point_attribute.h") +list( + APPEND draco_attributes_sources + "${draco_src_root}/attributes/attribute_octahedron_transform.cc" + "${draco_src_root}/attributes/attribute_octahedron_transform.h" + "${draco_src_root}/attributes/attribute_quantization_transform.cc" + "${draco_src_root}/attributes/attribute_quantization_transform.h" + "${draco_src_root}/attributes/attribute_transform.cc" + "${draco_src_root}/attributes/attribute_transform.h" + "${draco_src_root}/attributes/attribute_transform_data.h" + "${draco_src_root}/attributes/attribute_transform_type.h" + "${draco_src_root}/attributes/geometry_attribute.cc" + "${draco_src_root}/attributes/geometry_attribute.h" + "${draco_src_root}/attributes/geometry_indices.h" + "${draco_src_root}/attributes/point_attribute.cc" + "${draco_src_root}/attributes/point_attribute.h") list( APPEND draco_compression_attributes_dec_sources "${draco_src_root}/compression/attributes/attributes_decoder.cc" "${draco_src_root}/compression/attributes/attributes_decoder.h" + "${draco_src_root}/compression/attributes/attributes_decoder_interface.h" "${draco_src_root}/compression/attributes/kd_tree_attributes_decoder.cc" "${draco_src_root}/compression/attributes/kd_tree_attributes_decoder.h" "${draco_src_root}/compression/attributes/kd_tree_attributes_shared.h" @@ -107,7 +124,7 @@ list( "${draco_src_root}/compression/attributes/sequential_normal_attribute_decoder.h" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_decoder.cc" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_decoder.h" - ) +) list( APPEND @@ -128,7 +145,7 @@ list( "${draco_src_root}/compression/attributes/sequential_normal_attribute_encoder.h" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_encoder.cc" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_encoder.h" - ) +) list( @@ -160,7 +177,7 @@ list( "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_base.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_decoding_transform.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h" - ) +) list( APPEND @@ -192,7 +209,7 @@ list( "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_base.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_encoding_transform.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h" - ) +) list( APPEND @@ -217,27 +234,34 @@ list( "${draco_src_root}/compression/bit_coders/symbol_bit_encoder.cc" "${draco_src_root}/compression/bit_coders/symbol_bit_encoder.h") -list(APPEND draco_enc_config_sources - "${draco_src_root}/compression/config/compression_shared.h" - "${draco_src_root}/compression/config/draco_options.h" - "${draco_src_root}/compression/config/encoder_options.h" - "${draco_src_root}/compression/config/encoding_features.h") +list( + APPEND draco_enc_config_sources + "${draco_src_root}/compression/config/compression_shared.h" + "${draco_src_root}/compression/config/draco_options.h" + "${draco_src_root}/compression/config/encoder_options.h" + "${draco_src_root}/compression/config/encoding_features.h") -list(APPEND draco_dec_config_sources - "${draco_src_root}/compression/config/compression_shared.h" - "${draco_src_root}/compression/config/decoder_options.h" - "${draco_src_root}/compression/config/draco_options.h") +list( + APPEND draco_dec_config_sources + "${draco_src_root}/compression/config/compression_shared.h" + "${draco_src_root}/compression/config/decoder_options.h" + "${draco_src_root}/compression/config/draco_options.h") + +list(APPEND draco_compression_options_sources + "${draco_src_root}/compression/draco_compression_options.cc" + "${draco_src_root}/compression/draco_compression_options.h") list(APPEND draco_compression_decode_sources "${draco_src_root}/compression/decode.cc" "${draco_src_root}/compression/decode.h") -list(APPEND draco_compression_encode_sources - "${draco_src_root}/compression/encode.cc" - "${draco_src_root}/compression/encode.h" - "${draco_src_root}/compression/encode_base.h" - "${draco_src_root}/compression/expert_encode.cc" - "${draco_src_root}/compression/expert_encode.h") +list( + APPEND draco_compression_encode_sources + "${draco_src_root}/compression/encode.cc" + "${draco_src_root}/compression/encode.h" + "${draco_src_root}/compression/encode_base.h" + "${draco_src_root}/compression/expert_encode.cc" + "${draco_src_root}/compression/expert_encode.h") list( APPEND @@ -291,7 +315,7 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_decoder.h" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_decoder.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_decoder.h" - ) +) list( APPEND @@ -302,112 +326,126 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_encoder.h" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoder.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoder.h" - ) +) -list(APPEND draco_compression_entropy_sources - "${draco_src_root}/compression/entropy/ans.h" - "${draco_src_root}/compression/entropy/rans_symbol_coding.h" - "${draco_src_root}/compression/entropy/rans_symbol_decoder.h" - "${draco_src_root}/compression/entropy/rans_symbol_encoder.h" - "${draco_src_root}/compression/entropy/shannon_entropy.cc" - "${draco_src_root}/compression/entropy/shannon_entropy.h" - "${draco_src_root}/compression/entropy/symbol_decoding.cc" - "${draco_src_root}/compression/entropy/symbol_decoding.h" - "${draco_src_root}/compression/entropy/symbol_encoding.cc" - "${draco_src_root}/compression/entropy/symbol_encoding.h") +list( + APPEND draco_compression_entropy_sources + "${draco_src_root}/compression/entropy/ans.h" + "${draco_src_root}/compression/entropy/rans_symbol_coding.h" + "${draco_src_root}/compression/entropy/rans_symbol_decoder.h" + "${draco_src_root}/compression/entropy/rans_symbol_encoder.h" + "${draco_src_root}/compression/entropy/shannon_entropy.cc" + "${draco_src_root}/compression/entropy/shannon_entropy.h" + "${draco_src_root}/compression/entropy/symbol_decoding.cc" + "${draco_src_root}/compression/entropy/symbol_decoding.h" + "${draco_src_root}/compression/entropy/symbol_encoding.cc" + "${draco_src_root}/compression/entropy/symbol_encoding.h") -list(APPEND draco_core_sources - "${draco_src_root}/core/bit_utils.cc" - "${draco_src_root}/core/bit_utils.h" - "${draco_src_root}/core/bounding_box.cc" - "${draco_src_root}/core/bounding_box.h" - "${draco_src_root}/core/cycle_timer.cc" - "${draco_src_root}/core/cycle_timer.h" - "${draco_src_root}/core/data_buffer.cc" - "${draco_src_root}/core/data_buffer.h" - "${draco_src_root}/core/decoder_buffer.cc" - "${draco_src_root}/core/decoder_buffer.h" - "${draco_src_root}/core/divide.cc" - "${draco_src_root}/core/divide.h" - "${draco_src_root}/core/draco_index_type.h" - "${draco_src_root}/core/draco_index_type_vector.h" - "${draco_src_root}/core/draco_types.cc" - "${draco_src_root}/core/draco_types.h" - "${draco_src_root}/core/encoder_buffer.cc" - "${draco_src_root}/core/encoder_buffer.h" - "${draco_src_root}/core/hash_utils.cc" - "${draco_src_root}/core/hash_utils.h" - "${draco_src_root}/core/macros.h" - "${draco_src_root}/core/math_utils.h" - "${draco_src_root}/core/options.cc" - "${draco_src_root}/core/options.h" - "${draco_src_root}/core/quantization_utils.cc" - "${draco_src_root}/core/quantization_utils.h" - "${draco_src_root}/core/status.h" - "${draco_src_root}/core/status_or.h" - "${draco_src_root}/core/varint_decoding.h" - "${draco_src_root}/core/varint_encoding.h" - "${draco_src_root}/core/vector_d.h") +list( + APPEND draco_core_sources + "${draco_src_root}/core/bit_utils.cc" + "${draco_src_root}/core/bit_utils.h" + "${draco_src_root}/core/bounding_box.cc" + "${draco_src_root}/core/bounding_box.h" + "${draco_src_root}/core/constants.h" + "${draco_src_root}/core/cycle_timer.cc" + "${draco_src_root}/core/cycle_timer.h" + "${draco_src_root}/core/data_buffer.cc" + "${draco_src_root}/core/data_buffer.h" + "${draco_src_root}/core/decoder_buffer.cc" + "${draco_src_root}/core/decoder_buffer.h" + "${draco_src_root}/core/divide.cc" + "${draco_src_root}/core/divide.h" + "${draco_src_root}/core/draco_index_type.h" + "${draco_src_root}/core/draco_index_type_vector.h" + "${draco_src_root}/core/draco_types.cc" + "${draco_src_root}/core/draco_types.h" + "${draco_src_root}/core/draco_version.h" + "${draco_src_root}/core/encoder_buffer.cc" + "${draco_src_root}/core/encoder_buffer.h" + "${draco_src_root}/core/hash_utils.cc" + "${draco_src_root}/core/hash_utils.h" + "${draco_src_root}/core/macros.h" + "${draco_src_root}/core/math_utils.h" + "${draco_src_root}/core/options.cc" + "${draco_src_root}/core/options.h" + "${draco_src_root}/core/quantization_utils.cc" + "${draco_src_root}/core/quantization_utils.h" + "${draco_src_root}/core/status.h" + "${draco_src_root}/core/status_or.h" + "${draco_src_root}/core/varint_decoding.h" + "${draco_src_root}/core/varint_encoding.h" + "${draco_src_root}/core/vector_d.h") -list(APPEND draco_io_sources - "${draco_src_root}/io/file_reader_factory.cc" - "${draco_src_root}/io/file_reader_factory.h" - "${draco_src_root}/io/file_reader_interface.h" - "${draco_src_root}/io/file_utils.cc" - "${draco_src_root}/io/file_utils.h" - "${draco_src_root}/io/file_writer_factory.cc" - "${draco_src_root}/io/file_writer_factory.h" - "${draco_src_root}/io/file_writer_interface.h" - "${draco_src_root}/io/file_writer_utils.h" - "${draco_src_root}/io/file_writer_utils.cc" - "${draco_src_root}/io/mesh_io.cc" - "${draco_src_root}/io/mesh_io.h" - "${draco_src_root}/io/obj_decoder.cc" - "${draco_src_root}/io/obj_decoder.h" - "${draco_src_root}/io/obj_encoder.cc" - "${draco_src_root}/io/obj_encoder.h" - "${draco_src_root}/io/parser_utils.cc" - "${draco_src_root}/io/parser_utils.h" - "${draco_src_root}/io/ply_decoder.cc" - "${draco_src_root}/io/ply_decoder.h" - "${draco_src_root}/io/ply_encoder.cc" - "${draco_src_root}/io/ply_encoder.h" - "${draco_src_root}/io/ply_property_reader.h" - "${draco_src_root}/io/ply_property_writer.h" - "${draco_src_root}/io/ply_reader.cc" - "${draco_src_root}/io/ply_reader.h" - "${draco_src_root}/io/point_cloud_io.cc" - "${draco_src_root}/io/point_cloud_io.h" - "${draco_src_root}/io/stdio_file_reader.cc" - "${draco_src_root}/io/stdio_file_reader.h" - "${draco_src_root}/io/stdio_file_writer.cc" - "${draco_src_root}/io/stdio_file_writer.h") +list( + APPEND draco_io_sources + "${draco_src_root}/io/file_reader_factory.cc" + "${draco_src_root}/io/file_reader_factory.h" + "${draco_src_root}/io/file_reader_interface.h" + "${draco_src_root}/io/file_utils.cc" + "${draco_src_root}/io/file_utils.h" + "${draco_src_root}/io/file_writer_factory.cc" + "${draco_src_root}/io/file_writer_factory.h" + "${draco_src_root}/io/file_writer_interface.h" + "${draco_src_root}/io/file_writer_utils.h" + "${draco_src_root}/io/file_writer_utils.cc" + "${draco_src_root}/io/mesh_io.cc" + "${draco_src_root}/io/mesh_io.h" + "${draco_src_root}/io/obj_decoder.cc" + "${draco_src_root}/io/obj_decoder.h" + "${draco_src_root}/io/obj_encoder.cc" + "${draco_src_root}/io/obj_encoder.h" + "${draco_src_root}/io/parser_utils.cc" + "${draco_src_root}/io/parser_utils.h" + "${draco_src_root}/io/ply_decoder.cc" + "${draco_src_root}/io/ply_decoder.h" + "${draco_src_root}/io/ply_encoder.cc" + "${draco_src_root}/io/ply_encoder.h" + "${draco_src_root}/io/ply_property_reader.h" + "${draco_src_root}/io/ply_property_writer.h" + "${draco_src_root}/io/ply_reader.cc" + "${draco_src_root}/io/ply_reader.h" + "${draco_src_root}/io/stl_decoder.cc" + "${draco_src_root}/io/stl_decoder.h" + "${draco_src_root}/io/stl_encoder.cc" + "${draco_src_root}/io/stl_encoder.h" + "${draco_src_root}/io/point_cloud_io.cc" + "${draco_src_root}/io/point_cloud_io.h" + "${draco_src_root}/io/stdio_file_reader.cc" + "${draco_src_root}/io/stdio_file_reader.h" + "${draco_src_root}/io/stdio_file_writer.cc" + "${draco_src_root}/io/stdio_file_writer.h") -list(APPEND draco_mesh_sources - "${draco_src_root}/mesh/corner_table.cc" - "${draco_src_root}/mesh/corner_table.h" - "${draco_src_root}/mesh/corner_table_iterators.h" - "${draco_src_root}/mesh/mesh.cc" - "${draco_src_root}/mesh/mesh.h" - "${draco_src_root}/mesh/mesh_are_equivalent.cc" - "${draco_src_root}/mesh/mesh_are_equivalent.h" - "${draco_src_root}/mesh/mesh_attribute_corner_table.cc" - "${draco_src_root}/mesh/mesh_attribute_corner_table.h" - "${draco_src_root}/mesh/mesh_cleanup.cc" - "${draco_src_root}/mesh/mesh_cleanup.h" - "${draco_src_root}/mesh/mesh_misc_functions.cc" - "${draco_src_root}/mesh/mesh_misc_functions.h" - "${draco_src_root}/mesh/mesh_stripifier.cc" - "${draco_src_root}/mesh/mesh_stripifier.h" - "${draco_src_root}/mesh/triangle_soup_mesh_builder.cc" - "${draco_src_root}/mesh/triangle_soup_mesh_builder.h" - "${draco_src_root}/mesh/valence_cache.h") +list( + APPEND draco_mesh_sources + "${draco_src_root}/mesh/corner_table.cc" + "${draco_src_root}/mesh/corner_table.h" + "${draco_src_root}/mesh/corner_table_iterators.h" + "${draco_src_root}/mesh/mesh.cc" + "${draco_src_root}/mesh/mesh.h" + "${draco_src_root}/mesh/mesh_are_equivalent.cc" + "${draco_src_root}/mesh/mesh_are_equivalent.h" + "${draco_src_root}/mesh/mesh_attribute_corner_table.cc" + "${draco_src_root}/mesh/mesh_attribute_corner_table.h" + "${draco_src_root}/mesh/mesh_cleanup.cc" + "${draco_src_root}/mesh/mesh_cleanup.h" + "${draco_src_root}/mesh/mesh_features.cc" + "${draco_src_root}/mesh/mesh_features.h" + "${draco_src_root}/mesh/mesh_indices.h" + "${draco_src_root}/mesh/mesh_misc_functions.cc" + "${draco_src_root}/mesh/mesh_misc_functions.h" + "${draco_src_root}/mesh/mesh_stripifier.cc" + "${draco_src_root}/mesh/mesh_stripifier.h" + "${draco_src_root}/mesh/triangle_soup_mesh_builder.cc" + "${draco_src_root}/mesh/triangle_soup_mesh_builder.h" + "${draco_src_root}/mesh/valence_cache.h") -list(APPEND draco_point_cloud_sources - "${draco_src_root}/point_cloud/point_cloud.cc" - "${draco_src_root}/point_cloud/point_cloud.h" - "${draco_src_root}/point_cloud/point_cloud_builder.cc" - "${draco_src_root}/point_cloud/point_cloud_builder.h") +list( + APPEND draco_point_cloud_sources + "${draco_src_root}/point_cloud/point_cloud.cc" + "${draco_src_root}/point_cloud/point_cloud.h" + "${draco_src_root}/point_cloud/point_cloud_builder.cc" + "${draco_src_root}/point_cloud/point_cloud_builder.h") list( APPEND @@ -424,7 +462,7 @@ list( "${draco_src_root}/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_decoder.cc" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_decoder.h" - ) +) list( APPEND @@ -433,13 +471,18 @@ list( "${draco_src_root}/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_encoder.cc" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_encoder.h" - ) +) -list(APPEND draco_metadata_sources - "${draco_src_root}/metadata/geometry_metadata.cc" - "${draco_src_root}/metadata/geometry_metadata.h" - "${draco_src_root}/metadata/metadata.cc" - "${draco_src_root}/metadata/metadata.h") +list( + APPEND draco_metadata_sources + "${draco_src_root}/metadata/geometry_metadata.cc" + "${draco_src_root}/metadata/geometry_metadata.h" + "${draco_src_root}/metadata/metadata.cc" + "${draco_src_root}/metadata/metadata.h" + "${draco_src_root}/metadata/property_table.cc" + "${draco_src_root}/metadata/property_table.h" + "${draco_src_root}/metadata/structural_metadata.cc" + "${draco_src_root}/metadata/structural_metadata.h") list(APPEND draco_metadata_enc_sources "${draco_src_root}/metadata/metadata_encoder.cc" @@ -465,7 +508,7 @@ list( APPEND draco_js_dec_sources "${draco_src_root}/javascript/emscripten/decoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_decoder_glue_wrapper.cc" - ) +) list( APPEND draco_js_enc_sources @@ -477,14 +520,14 @@ list( draco_animation_js_dec_sources "${draco_src_root}/javascript/emscripten/animation_decoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc" - ) +) list( APPEND draco_animation_js_enc_sources "${draco_src_root}/javascript/emscripten/animation_encoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc" - ) +) list(APPEND draco_unity_plug_sources "${draco_src_root}/unity/draco_unity_plugin.cc" @@ -494,49 +537,133 @@ list(APPEND draco_maya_plug_sources "${draco_src_root}/maya/draco_maya_plugin.cc" "${draco_src_root}/maya/draco_maya_plugin.h") +if(DRACO_TRANSCODER_SUPPORTED) + list( + APPEND draco_animation_sources + "${draco_src_root}/animation/animation.cc" + "${draco_src_root}/animation/animation.h" + "${draco_src_root}/animation/node_animation_data.h" + "${draco_src_root}/animation/skin.cc" + "${draco_src_root}/animation/skin.h") + + list( + APPEND draco_io_sources + "${draco_src_root}/io/gltf_decoder.cc" + "${draco_src_root}/io/gltf_decoder.h" + "${draco_src_root}/io/gltf_encoder.cc" + "${draco_src_root}/io/gltf_encoder.h" + "${draco_src_root}/io/gltf_utils.cc" + "${draco_src_root}/io/gltf_utils.h" + "${draco_src_root}/io/image_compression_options.h" + "${draco_src_root}/io/scene_io.cc" + "${draco_src_root}/io/scene_io.h" + "${draco_src_root}/io/texture_io.cc" + "${draco_src_root}/io/texture_io.h" + "${draco_src_root}/io/tiny_gltf_utils.cc" + "${draco_src_root}/io/tiny_gltf_utils.h") + + list( + APPEND draco_material_sources + "${draco_src_root}/material/material.cc" + "${draco_src_root}/material/material.h" + "${draco_src_root}/material/material_library.cc" + "${draco_src_root}/material/material_library.h") + + list( + APPEND draco_mesh_sources + "${draco_src_root}/mesh/mesh_connected_components.h" + "${draco_src_root}/mesh/mesh_splitter.cc" + "${draco_src_root}/mesh/mesh_splitter.h" + "${draco_src_root}/mesh/mesh_utils.cc" + "${draco_src_root}/mesh/mesh_utils.h") + + list( + APPEND draco_scene_sources + "${draco_src_root}/scene/instance_array.cc" + "${draco_src_root}/scene/instance_array.h" + "${draco_src_root}/scene/light.cc" + "${draco_src_root}/scene/light.h" + "${draco_src_root}/scene/mesh_group.h" + "${draco_src_root}/scene/scene.cc" + "${draco_src_root}/scene/scene.h" + "${draco_src_root}/scene/scene_are_equivalent.cc" + "${draco_src_root}/scene/scene_are_equivalent.h" + "${draco_src_root}/scene/scene_indices.h" + "${draco_src_root}/scene/scene_node.h" + "${draco_src_root}/scene/scene_utils.cc" + "${draco_src_root}/scene/scene_utils.h" + "${draco_src_root}/scene/trs_matrix.cc" + "${draco_src_root}/scene/trs_matrix.h") + + list( + APPEND draco_texture_sources + "${draco_src_root}/texture/source_image.cc" + "${draco_src_root}/texture/source_image.h" + "${draco_src_root}/texture/texture.h" + "${draco_src_root}/texture/texture_library.cc" + "${draco_src_root}/texture/texture_library.h" + "${draco_src_root}/texture/texture_map.cc" + "${draco_src_root}/texture/texture_map.h" + "${draco_src_root}/texture/texture_transform.cc" + "${draco_src_root}/texture/texture_transform.h" + "${draco_src_root}/texture/texture_utils.cc" + "${draco_src_root}/texture/texture_utils.h") + + +endif() + # # Draco targets. # if(EMSCRIPTEN AND DRACO_JS_GLUE) # Draco decoder and encoder "executable" targets in various flavors for - # Emsscripten. - list(APPEND draco_decoder_src - ${draco_attributes_sources} - ${draco_compression_attributes_dec_sources} - ${draco_compression_attributes_pred_schemes_dec_sources} - ${draco_compression_bit_coders_sources} - ${draco_compression_decode_sources} - ${draco_compression_entropy_sources} - ${draco_compression_mesh_traverser_sources} - ${draco_compression_mesh_dec_sources} - ${draco_compression_point_cloud_dec_sources} - ${draco_core_sources} - ${draco_dec_config_sources} - ${draco_js_dec_sources} - ${draco_mesh_sources} - ${draco_metadata_dec_sources} - ${draco_metadata_sources} - ${draco_point_cloud_sources} - ${draco_points_dec_sources}) + # Emscripten. - list(APPEND draco_encoder_src - ${draco_attributes_sources} - ${draco_compression_attributes_enc_sources} - ${draco_compression_attributes_pred_schemes_enc_sources} - ${draco_compression_bit_coders_sources} - ${draco_compression_encode_sources} - ${draco_compression_entropy_sources} - ${draco_compression_mesh_traverser_sources} - ${draco_compression_mesh_enc_sources} - ${draco_compression_point_cloud_enc_sources} - ${draco_core_sources} - ${draco_enc_config_sources} - ${draco_js_enc_sources} - ${draco_mesh_sources} - ${draco_metadata_enc_sources} - ${draco_metadata_sources} - ${draco_point_cloud_sources} - ${draco_points_enc_sources}) + if(DRACO_TRANSCODER_SUPPORTED) + message(FATAL_ERROR "The transcoder is not supported in Emscripten.") + endif() + + list( + APPEND draco_decoder_src + ${draco_attributes_sources} + ${draco_compression_attributes_dec_sources} + ${draco_compression_attributes_pred_schemes_dec_sources} + ${draco_compression_bit_coders_sources} + ${draco_compression_decode_sources} + ${draco_compression_entropy_sources} + ${draco_compression_mesh_traverser_sources} + ${draco_compression_mesh_dec_sources} + ${draco_compression_options_sources} + ${draco_compression_point_cloud_dec_sources} + ${draco_core_sources} + ${draco_dec_config_sources} + ${draco_js_dec_sources} + ${draco_mesh_sources} + ${draco_metadata_dec_sources} + ${draco_metadata_sources} + ${draco_point_cloud_sources} + ${draco_points_dec_sources}) + + list( + APPEND draco_encoder_src + ${draco_attributes_sources} + ${draco_compression_attributes_enc_sources} + ${draco_compression_attributes_pred_schemes_enc_sources} + ${draco_compression_bit_coders_sources} + ${draco_compression_encode_sources} + ${draco_compression_entropy_sources} + ${draco_compression_mesh_traverser_sources} + ${draco_compression_mesh_enc_sources} + ${draco_compression_options_sources} + ${draco_compression_point_cloud_enc_sources} + ${draco_core_sources} + ${draco_enc_config_sources} + ${draco_js_enc_sources} + ${draco_mesh_sources} + ${draco_metadata_enc_sources} + ${draco_metadata_sources} + ${draco_point_cloud_sources} + ${draco_points_enc_sources}) list(APPEND draco_js_dec_idl "${draco_src_root}/javascript/emscripten/draco_web_decoder.idl") @@ -561,10 +688,10 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) set(draco_decoder_glue_path "${draco_build}/glue_decoder") set(draco_encoder_glue_path "${draco_build}/glue_encoder") - draco_generate_emscripten_glue(INPUT_IDL ${draco_js_dec_idl} OUTPUT_PATH - ${draco_decoder_glue_path}) - draco_generate_emscripten_glue(INPUT_IDL ${draco_js_enc_idl} OUTPUT_PATH - ${draco_encoder_glue_path}) + draco_generate_emscripten_glue(INPUT_IDL ${draco_js_dec_idl} + OUTPUT_PATH ${draco_decoder_glue_path}) + draco_generate_emscripten_glue(INPUT_IDL ${draco_js_enc_idl} + OUTPUT_PATH ${draco_encoder_glue_path}) if(DRACO_DECODER_ATTRIBUTE_DEDUPLICATION) list(APPEND draco_decoder_features @@ -572,45 +699,28 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) "DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED") endif() - draco_add_emscripten_executable(NAME - draco_decoder - SOURCES - ${draco_decoder_src} - DEFINES - ${draco_defines} - FEATURES - ${draco_decoder_features} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoDecoderModule\"" - GLUE_PATH - ${draco_decoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_decoder_sources}) + draco_add_emscripten_executable( + NAME draco_decoder + SOURCES ${draco_decoder_src} + DEFINES ${draco_defines} + FEATURES ${draco_decoder_features} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoDecoderModule\"" + GLUE_PATH ${draco_decoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_decoder_sources}) draco_add_emscripten_executable( - NAME - draco_encoder - SOURCES - ${draco_encoder_src} - DEFINES - ${draco_defines} - FEATURES - DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED - DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoEncoderModule\"" - GLUE_PATH - ${draco_encoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_sources}) + NAME draco_encoder + SOURCES ${draco_encoder_src} + DEFINES ${draco_defines} + FEATURES DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED + DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoEncoderModule\"" + GLUE_PATH ${draco_encoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_sources}) if(DRACO_ANIMATION_ENCODING) set(draco_anim_decoder_glue_path "${draco_build}/glue_animation_decoder") @@ -622,186 +732,270 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) OUTPUT_PATH ${draco_anim_encoder_glue_path}) draco_add_emscripten_executable( - NAME - draco_animation_decoder - SOURCES - ${draco_animation_dec_sources} - ${draco_animation_js_dec_sources} - ${draco_animation_sources} - ${draco_decoder_src} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoAnimationDecoderModule\"" - GLUE_PATH - ${draco_anim_decoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_decoder_sources}) + NAME draco_animation_decoder + SOURCES ${draco_animation_dec_sources} ${draco_animation_js_dec_sources} + ${draco_animation_sources} ${draco_decoder_src} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoAnimationDecoderModule\"" + GLUE_PATH ${draco_anim_decoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_decoder_sources}) draco_add_emscripten_executable( - NAME - draco_animation_encoder - SOURCES - ${draco_animation_enc_sources} - ${draco_animation_js_enc_sources} - ${draco_animation_sources} - ${draco_encoder_src} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoAnimationEncoderModule\"" - GLUE_PATH - ${draco_anim_encoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_sources}) + NAME draco_animation_encoder + SOURCES ${draco_animation_enc_sources} ${draco_animation_js_enc_sources} + ${draco_animation_sources} ${draco_encoder_src} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoAnimationEncoderModule\"" + GLUE_PATH ${draco_anim_encoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_sources}) endif() else() # Standard Draco libs, encoder and decoder. Object collections that mirror the # Draco directory structure. - draco_add_library(NAME draco_attributes TYPE OBJECT SOURCES - ${draco_attributes_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_compression_attributes_dec - OBJECT - ${draco_compression_attributes_dec_sources} - TYPE - OBJECT - SOURCES - ${draco_compression_attributes_dec_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_compression_attributes_enc TYPE OBJECT SOURCES - ${draco_compression_attributes_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_attributes_pred_schemes_dec TYPE - OBJECT SOURCES - ${draco_compression_attributes_pred_schemes_dec_sources}) - draco_add_library(NAME draco_compression_attributes_pred_schemes_enc TYPE - OBJECT SOURCES - ${draco_compression_attributes_pred_schemes_enc_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_bit_coders TYPE OBJECT SOURCES - ${draco_compression_bit_coders_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_enc_config TYPE OBJECT SOURCES - ${draco_enc_config_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_dec_config TYPE OBJECT SOURCES - ${draco_dec_config_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_decode TYPE OBJECT SOURCES - ${draco_compression_decode_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_encode TYPE OBJECT SOURCES - ${draco_compression_encode_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_entropy TYPE OBJECT SOURCES - ${draco_compression_entropy_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_traverser TYPE OBJECT SOURCES - ${draco_compression_mesh_traverser_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_dec TYPE OBJECT SOURCES - ${draco_compression_mesh_dec_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_enc TYPE OBJECT SOURCES - ${draco_compression_mesh_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_point_cloud_dec TYPE OBJECT SOURCES - ${draco_compression_point_cloud_dec_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_point_cloud_enc TYPE OBJECT SOURCES - ${draco_compression_point_cloud_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_core TYPE OBJECT SOURCES ${draco_core_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_io TYPE OBJECT SOURCES ${draco_io_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_mesh TYPE OBJECT SOURCES ${draco_mesh_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata_dec TYPE OBJECT SOURCES - ${draco_metadata_dec_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata_enc TYPE OBJECT SOURCES - ${draco_metadata_enc_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata TYPE OBJECT SOURCES - ${draco_metadata_sources} DEFINES ${draco_defines} INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_animation_dec TYPE OBJECT SOURCES - ${draco_animation_dec_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_animation_enc TYPE OBJECT SOURCES - ${draco_animation_enc_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_animation TYPE OBJECT SOURCES - ${draco_animation_sources} DEFINES ${draco_defines} INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_point_cloud TYPE OBJECT SOURCES - ${draco_point_cloud_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_points_dec - TYPE - OBJECT - SOURCES - ${draco_points_common_sources} - ${draco_points_dec_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) - draco_add_library(NAME - draco_points_enc - TYPE - OBJECT - SOURCES - ${draco_points_common_sources} - ${draco_points_enc_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) + draco_add_library( + NAME draco_attributes + TYPE OBJECT + SOURCES ${draco_attributes_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_dec OBJECT + ${draco_compression_attributes_dec_sources} + TYPE OBJECT + SOURCES ${draco_compression_attributes_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_enc + TYPE OBJECT + SOURCES ${draco_compression_attributes_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_pred_schemes_dec + TYPE OBJECT + SOURCES ${draco_compression_attributes_pred_schemes_dec_sources}) + draco_add_library( + NAME draco_compression_attributes_pred_schemes_enc + TYPE OBJECT + SOURCES ${draco_compression_attributes_pred_schemes_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_bit_coders + TYPE OBJECT + SOURCES ${draco_compression_bit_coders_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_enc_config + TYPE OBJECT + SOURCES ${draco_enc_config_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_dec_config + TYPE OBJECT + SOURCES ${draco_dec_config_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_decode + TYPE OBJECT + SOURCES ${draco_compression_decode_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_encode + TYPE OBJECT + SOURCES ${draco_compression_encode_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_entropy + TYPE OBJECT + SOURCES ${draco_compression_entropy_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_traverser + TYPE OBJECT + SOURCES ${draco_compression_mesh_traverser_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_dec + TYPE OBJECT + SOURCES ${draco_compression_mesh_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_enc + TYPE OBJECT + SOURCES ${draco_compression_mesh_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_options + TYPE OBJECT + SOURCES ${draco_compression_options_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_point_cloud_dec + TYPE OBJECT + SOURCES ${draco_compression_point_cloud_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_point_cloud_enc + TYPE OBJECT + SOURCES ${draco_compression_point_cloud_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_core + TYPE OBJECT + SOURCES ${draco_core_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_io + TYPE OBJECT + SOURCES ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_mesh + TYPE OBJECT + SOURCES ${draco_mesh_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata_dec + TYPE OBJECT + SOURCES ${draco_metadata_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata_enc + TYPE OBJECT + SOURCES ${draco_metadata_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata + TYPE OBJECT + SOURCES ${draco_metadata_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation_dec + TYPE OBJECT + SOURCES ${draco_animation_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation_enc + TYPE OBJECT + SOURCES ${draco_animation_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation + TYPE OBJECT + SOURCES ${draco_animation_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_point_cloud + TYPE OBJECT + SOURCES ${draco_point_cloud_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_points_dec + TYPE OBJECT + SOURCES ${draco_points_common_sources} ${draco_points_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_points_enc + TYPE OBJECT + SOURCES ${draco_points_common_sources} ${draco_points_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - set(draco_object_library_deps - draco_attributes - draco_compression_attributes_dec - draco_compression_attributes_enc - draco_compression_attributes_pred_schemes_dec - draco_compression_attributes_pred_schemes_enc - draco_compression_bit_coders - draco_compression_decode - draco_compression_encode - draco_compression_entropy - draco_compression_mesh_dec - draco_compression_mesh_enc - draco_compression_point_cloud_dec - draco_compression_point_cloud_enc - draco_core - draco_dec_config - draco_enc_config - draco_io - draco_mesh - draco_metadata - draco_metadata_dec - draco_metadata_enc - draco_animation - draco_animation_dec - draco_animation_enc - draco_point_cloud - draco_points_dec - draco_points_enc) + if(DRACO_TRANSCODER_SUPPORTED) + if(MSVC) + # TODO(https://github.com/google/draco/issues/826) + set_source_files_properties("${draco_src_root}/io/gltf_decoder.cc" + PROPERTIES COMPILE_OPTIONS "/Od") + endif() + + draco_add_library( + NAME draco_material + TYPE OBJECT + SOURCES ${draco_material_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + draco_add_library( + NAME draco_scene + TYPE OBJECT + SOURCES ${draco_scene_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + draco_add_library( + NAME draco_texture + TYPE OBJECT + SOURCES ${draco_texture_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + endif() + + list( + APPEND draco_object_library_deps + draco_attributes + draco_compression_attributes_dec + draco_compression_attributes_enc + draco_compression_attributes_pred_schemes_dec + draco_compression_attributes_pred_schemes_enc + draco_compression_bit_coders + draco_compression_decode + draco_compression_encode + draco_compression_entropy + draco_compression_mesh_dec + draco_compression_mesh_enc + draco_compression_options + draco_compression_point_cloud_dec + draco_compression_point_cloud_enc + draco_core + draco_dec_config + draco_enc_config + draco_io + draco_mesh + draco_metadata + draco_metadata_dec + draco_metadata_enc + draco_animation + draco_animation_dec + draco_animation_enc + draco_point_cloud + draco_points_dec + draco_points_enc) + + if(DRACO_TRANSCODER_SUPPORTED) + list(APPEND draco_object_library_deps draco_material draco_scene + draco_texture) + + endif() # Library targets that consume the object collections. if(MSVC) @@ -809,56 +1003,48 @@ else() # that the exported symbols are part of the DLL target. The unfortunate side # effect of this is that a single configuration cannot output both the # static library and the DLL: This results in an either/or situation. - # Windows users of the draco build can have a DLL and an import library, - # or they can have a static library; they cannot have both from a single + # Windows users of the draco build can have a DLL and an import library, or + # they can have a static library; they cannot have both from a single # configuration of the build. if(BUILD_SHARED_LIBS) set(draco_lib_type SHARED) else() set(draco_lib_type STATIC) endif() - draco_add_library(NAME - draco - OUTPUT_NAME - draco - TYPE - ${draco_lib_type} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - ${draco_object_library_deps}) + draco_add_library( + NAME draco + OUTPUT_NAME draco + TYPE ${draco_lib_type} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS ${draco_object_library_deps} + LIB_DEPS ${draco_lib_deps}) + add_library(draco::draco ALIAS draco) else() - draco_add_library(NAME - draco_static - OUTPUT_NAME - draco - TYPE - STATIC - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - ${draco_object_library_deps}) + draco_add_library( + NAME draco_static + OUTPUT_NAME draco + TYPE STATIC + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS ${draco_object_library_deps} + LIB_DEPS ${draco_lib_deps}) if(BUILD_SHARED_LIBS) - draco_add_library(NAME - draco_shared - SOURCES - "${draco_src_root}/core/draco_version.h" - OUTPUT_NAME - draco - TYPE - SHARED - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - draco_static) + draco_add_library( + NAME draco_shared + SOURCES "${draco_src_root}/core/draco_version.h" + OUTPUT_NAME draco + TYPE SHARED + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS draco_static) + add_library(draco::draco ALIAS draco_shared) + set_target_properties(draco_shared PROPERTIES EXPORT_NAME draco) + else() + add_library(draco::draco ALIAS draco_static) + set_target_properties(draco_static PROPERTIES EXPORT_NAME draco) endif() endif() @@ -869,22 +1055,20 @@ else() set(unity_decoder_lib_type MODULE) endif() - draco_add_library(NAME draco_unity_plugin TYPE OBJECT SOURCES - ${draco_unity_plug_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_unity_plugin + TYPE OBJECT + SOURCES ${draco_unity_plug_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - draco_add_library(NAME - dracodec_unity - TYPE - ${unity_decoder_lib_type} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - draco_unity_plugin - LIB_DEPS - ${draco_plugin_dependency}) + draco_add_library( + NAME dracodec_unity + TYPE ${unity_decoder_lib_type} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS draco_unity_plugin + LIB_DEPS ${draco_plugin_dependency}) # For Mac, we need to build a .bundle for the unity plugin. if(APPLE) @@ -893,22 +1077,20 @@ else() endif() if(DRACO_MAYA_PLUGIN) - draco_add_library(NAME draco_maya_plugin TYPE OBJECT SOURCES - ${draco_maya_plug_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_maya_plugin + TYPE OBJECT + SOURCES ${draco_maya_plug_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_maya_wrapper - TYPE - MODULE - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - draco_maya_plugin - LIB_DEPS - ${draco_plugin_dependency}) + draco_add_library( + NAME draco_maya_wrapper + TYPE MODULE + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS draco_maya_plugin + LIB_DEPS ${draco_plugin_dependency}) # For Mac, we need to build a .bundle for the plugin. if(APPLE) @@ -917,29 +1099,44 @@ else() endif() # Draco app targets. - draco_add_executable(NAME - draco_decoder - SOURCES - "${draco_src_root}/tools/draco_decoder.cc" - ${draco_io_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - ${draco_dependency}) + draco_add_executable( + NAME draco_decoder + SOURCES "${draco_src_root}/tools/draco_decoder.cc" ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) - draco_add_executable(NAME - draco_encoder - SOURCES - "${draco_src_root}/tools/draco_encoder.cc" - ${draco_io_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - ${draco_dependency}) + draco_add_executable( + NAME draco_encoder + SOURCES "${draco_src_root}/tools/draco_encoder.cc" ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + + if(DRACO_TRANSCODER_SUPPORTED) + draco_add_executable( + NAME draco_transcoder + SOURCES "${draco_src_root}/tools/draco_transcoder.cc" + "${draco_src_root}/tools/draco_transcoder_lib.cc" + "${draco_src_root}/tools/draco_transcoder_lib.h" + ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + + if(DRACO_SIMPLIFIER_SUPPORTED) + draco_add_executable( + NAME draco_simplifier + SOURCES ${draco_pipeline_proto_header} + "${draco_src_root}/tools/draco_simplifier.cc" + "${draco_src_root}/tools/draco_simplifier_lib.cc" + "${draco_src_root}/tools/draco_simplifier_lib.h" + ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + endif() + endif() draco_setup_install_target() draco_setup_test_targets() diff --git a/Engine/lib/assimp/contrib/draco/README.md b/Engine/lib/assimp/contrib/draco/README.md index 0d980b387..4cc717c8d 100644 --- a/Engine/lib/assimp/contrib/draco/README.md +++ b/Engine/lib/assimp/contrib/draco/README.md @@ -2,10 +2,93 @@

-[![Build Status](https://github.com/google/draco/workflows/Build/badge.svg)](https://github.com/google/draco/actions?query=workflow%3ABuild) +[![draco-ci](https://github.com/google/draco/workflows/draco-ci/badge.svg?branch=master)](https://github.com/google/draco/actions/workflows/ci.yml) News ======= + +Attention GStatic users: the Draco team strongly recommends using the versioned +URLs for accessing Draco GStatic content. If you are using the URLs that include +the `v1/decoders` substring within the URL, edge caching and GStatic propagation +delays can result in transient errors that can be difficult to diagnose when +new Draco releases are launched. To avoid the issue pin your sites to a +versioned release. + +### Version 1.5.6 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.6, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.6/* +* The CMake flag DRACO_DEBUG_MSVC_WARNINGS has been replaced with + DRACO_DEBUG_COMPILER_WARNINGS, and the behavior has changed. It is now a + boolean flag defined in draco_options.cmake. +* Bug fixes. +* Security fixes. + +### Version 1.5.5 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.5, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.5/* +* Bug fix: https://github.com/google/draco/issues/935 + +### Version 1.5.4 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.4, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.4/* +* Added partial support for glTF extensions EXT_mesh_features and + EXT_structural_metadata. +* Bug fixes. +* Security fixes. + +### Version 1.5.3 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.3, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.3/* +* Bug fixes. + +### Version 1.5.2 release +* This is the same as v1.5.1 with the following two bug fixes: + * Fixes DRACO_TRANSCODER_SUPPORTED enabled builds. + * ABI version updated. + +### Version 1.5.1 release +* Adds assertion enabled Emscripten builds to the release, and a subset of the + assertion enabled builds to GStatic. See the file listing below. +* Custom paths to third party dependencies are now supported. See BUILDING.md + for more information. +* The CMake configuration file draco-config.cmake is now tested and known to + work for using Draco in Linux, MacOS, and Windows CMake projects. See the + `install_test` subdirectory of `src/draco/tools` for more information. +* Bug fixes. + +### Version 1.5.0 release +* Adds the draco_transcoder tool. See the section below on the glTF transcoding + tool, and BUILDING.md for build and dependency information. +* Some changes to configuration variables have been made for this release: + - The DRACO_GLTF flag has been renamed to DRACO_GLTF_BITSTREAM to help + increase understanding of its purpose, which is to limit Draco features to + those included in the Draco glTF specification. + - Variables exported in CMake via draco-config.cmake and find-draco.cmake + (formerly FindDraco.cmake) have been renamed. It's unlikely that this + impacts any existing projects as the aforementioned files were not formed + correctly. See [PR775](https://github.com/google/draco/pull/775) for full + details of the changes. +* A CMake version file has been added. +* The CMake install target now uses absolute paths direct from CMake instead + of building them using CMAKE_INSTALL_PREFIX. This was done to make Draco + easier to use for downstream packagers and should have little to no impact on + users picking up Draco from source. +* Certain MSVC warnings have had their levels changed via compiler flag to + reduce the amount of noise output by the MSVC compilers. Set MSVC warning + level to 4, or define DRACO_DEBUG_MSVC_WARNINGS at CMake configuration time + to restore previous behavior. +* Bug fixes. + +### Version 1.4.3 release +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.4.3, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.4.3/* +* Bug fixes + ### Version 1.4.1 release * Using the versioned www.gstatic.com WASM and Javascript decoders is now recommended. To use v1.4.1, use this URL: @@ -129,6 +212,7 @@ _**Contents**_ * [Encoding Tool](#encoding-tool) * [Encoding Point Clouds](#encoding-point-clouds) * [Decoding Tool](#decoding-tool) + * [glTF Transcoding Tool](#gltf-transcoding-tool) * [C++ Decoder API](#c-decoder-api) * [Javascript Encoder API](#javascript-encoder-api) * [Javascript Decoder API](#javascript-decoder-api) @@ -136,6 +220,7 @@ _**Contents**_ * [Metadata API](#metadata-api) * [NPM Package](#npm-package) * [three.js Renderer Example](#threejs-renderer-example) + * [GStatic Javascript Builds](#gstatic-javascript-builds) * [Support](#support) * [License](#license) * [References](#references) @@ -170,16 +255,18 @@ Command Line Applications ------------------------ The default target created from the build files will be the `draco_encoder` -and `draco_decoder` command line applications. For both applications, if you -run them without any arguments or `-h`, the applications will output usage and -options. +and `draco_decoder` command line applications. Additionally, `draco_transcoder` +is generated when CMake is run with the DRACO_TRANSCODER_SUPPORTED variable set +to ON (see [BUILDING](BUILDING.md#transcoder) for more details). For all +applications, if you run them without any arguments or `-h`, the applications +will output usage and options. Encoding Tool ------------- -`draco_encoder` will read OBJ or PLY files as input, and output Draco-encoded -files. We have included Stanford's [Bunny] mesh for testing. The basic command -line looks like this: +`draco_encoder` will read OBJ, STL or PLY files as input, and output +Draco-encoded files. We have included Stanford's [Bunny] mesh for testing. The +basic command line looks like this: ~~~~~ bash ./draco_encoder -i testdata/bun_zipper.ply -o out.drc @@ -232,15 +319,34 @@ and denser point clouds. Decoding Tool ------------- -`draco_decoder` will read Draco files as input, and output OBJ or PLY files. -The basic command line looks like this: +`draco_decoder` will read Draco files as input, and output OBJ, STL or PLY +files. The basic command line looks like this: ~~~~~ bash ./draco_decoder -i in.drc -o out.obj ~~~~~ +glTF Transcoding Tool +--------------------- + +`draco_transcoder` can be used to add Draco compression to glTF assets. The +basic command line looks like this: + +~~~~~ bash +./draco_transcoder -i in.glb -o out.glb +~~~~~ + +This command line will add geometry compression to all meshes in the `in.glb` +file. Quantization values for different glTF attributes can be specified +similarly to the `draco_encoder` tool. For example `-qp` can be used to define +quantization of the position attribute: + +~~~~~ bash +./draco_transcoder -i in.glb -o out.glb -qp 12 +~~~~~ + C++ Decoder API -------------- +--------------- If you'd like to add decoding to your applications you will need to include the `draco_dec` library. In order to use the Draco decoder you need to @@ -442,6 +548,30 @@ Javascript decoder using the `three.js` renderer. Please see the [javascript/example/README.md](javascript/example/README.md) file for more information. +GStatic Javascript Builds +========================= + +Prebuilt versions of the Emscripten-built Draco javascript decoders are hosted +on www.gstatic.com in version labeled directories: + +https://www.gstatic.com/draco/versioned/decoders/VERSION/* + +As of the v1.4.3 release the files available are: + +- [draco_decoder.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder.js) +- [draco_decoder.wasm](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder.wasm) +- [draco_decoder_gltf.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder_gltf.js) +- [draco_decoder_gltf.wasm](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder_gltf.wasm) +- [draco_wasm_wrapper.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_wasm_wrapper.js) +- [draco_wasm_wrapper_gltf.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_wasm_wrapper_gltf.js) + +Beginning with the v1.5.1 release assertion enabled builds of the following +files are available: + +- [draco_decoder.js](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_decoder.js) +- [draco_decoder.wasm](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_decoder.wasm) +- [draco_wasm_wrapper.js](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_wasm_wrapper.js) + Support ======= diff --git a/Engine/lib/assimp/contrib/draco/cmake/DracoConfig.cmake b/Engine/lib/assimp/contrib/draco/cmake/DracoConfig.cmake deleted file mode 100644 index be5e1faef..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/DracoConfig.cmake +++ /dev/null @@ -1,3 +0,0 @@ -@PACKAGE_INIT@ -set_and_check(draco_INCLUDE_DIR "@PACKAGE_draco_include_install_dir@") -set_and_check(draco_LIBRARY_DIR "@PACKAGE_draco_lib_install_dir@") diff --git a/Engine/lib/assimp/contrib/draco/cmake/FindDraco.cmake b/Engine/lib/assimp/contrib/draco/cmake/FindDraco.cmake deleted file mode 100644 index 0a9193065..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/FindDraco.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Finddraco -# -# Locates draco and sets the following variables: -# -# draco_FOUND draco_INCLUDE_DIRS draco_LIBARY_DIRS draco_LIBRARIES -# draco_VERSION_STRING -# -# draco_FOUND is set to YES only when all other variables are successfully -# configured. - -unset(draco_FOUND) -unset(draco_INCLUDE_DIRS) -unset(draco_LIBRARY_DIRS) -unset(draco_LIBRARIES) -unset(draco_VERSION_STRING) - -mark_as_advanced(draco_FOUND) -mark_as_advanced(draco_INCLUDE_DIRS) -mark_as_advanced(draco_LIBRARY_DIRS) -mark_as_advanced(draco_LIBRARIES) -mark_as_advanced(draco_VERSION_STRING) - -set(draco_version_file_no_prefix "draco/src/draco/core/draco_version.h") - -# Set draco_INCLUDE_DIRS -find_path(draco_INCLUDE_DIRS NAMES "${draco_version_file_no_prefix}") - -# Extract the version string from draco_version.h. -if(draco_INCLUDE_DIRS) - set(draco_version_file - "${draco_INCLUDE_DIRS}/draco/src/draco/core/draco_version.h") - file(STRINGS "${draco_version_file}" draco_version REGEX "kdracoVersion") - list(GET draco_version 0 draco_version) - string(REPLACE "static const char kdracoVersion[] = " "" draco_version - "${draco_version}") - string(REPLACE ";" "" draco_version "${draco_version}") - string(REPLACE "\"" "" draco_version "${draco_version}") - set(draco_VERSION_STRING ${draco_version}) -endif() - -# Find the library. -if(BUILD_SHARED_LIBS) - find_library(draco_LIBRARIES NAMES draco.dll libdraco.dylib libdraco.so) -else() - find_library(draco_LIBRARIES NAMES draco.lib libdraco.a) -endif() - -# Store path to library. -get_filename_component(draco_LIBRARY_DIRS ${draco_LIBRARIES} DIRECTORY) - -if(draco_INCLUDE_DIRS - AND draco_LIBRARY_DIRS - AND draco_LIBRARIES - AND draco_VERSION_STRING) - set(draco_FOUND YES) -endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/compiler_flags.cmake b/Engine/lib/assimp/contrib/draco/cmake/compiler_flags.cmake deleted file mode 100644 index 8750e6f7d..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/compiler_flags.cmake +++ /dev/null @@ -1,220 +0,0 @@ -if(DRACO_CMAKE_COMPILER_FLAGS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_COMPILER_FLAGS_CMAKE_ 1) - -include(CheckCCompilerFlag) -include(CheckCXXCompilerFlag) -include("${draco_root}/cmake/compiler_tests.cmake") - -# Strings used to cache failed C/CXX flags. -set(DRACO_FAILED_C_FLAGS) -set(DRACO_FAILED_CXX_FLAGS) - -# Checks C compiler for support of $c_flag. Adds $c_flag to $CMAKE_C_FLAGS when -# the compile test passes. Caches $c_flag in $DRACO_FAILED_C_FLAGS when the test -# fails. -macro(add_c_flag_if_supported c_flag) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${c_flag}" C_FLAG_FOUND) - unset(C_FLAG_FAILED CACHE) - string(FIND "${DRACO_FAILED_C_FLAGS}" "${c_flag}" C_FLAG_FAILED) - - if(${C_FLAG_FOUND} EQUAL -1 AND ${C_FLAG_FAILED} EQUAL -1) - unset(C_FLAG_SUPPORTED CACHE) - message("Checking C compiler flag support for: " ${c_flag}) - check_c_compiler_flag("${c_flag}" C_FLAG_SUPPORTED) - if(${C_FLAG_SUPPORTED}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${c_flag}" CACHE STRING "") - else() - set(DRACO_FAILED_C_FLAGS - "${DRACO_FAILED_C_FLAGS} ${c_flag}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks C++ compiler for support of $cxx_flag. Adds $cxx_flag to -# $CMAKE_CXX_FLAGS when the compile test passes. Caches $c_flag in -# $DRACO_FAILED_CXX_FLAGS when the test fails. -macro(add_cxx_flag_if_supported cxx_flag) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) - unset(CXX_FLAG_FAILED CACHE) - string(FIND "${DRACO_FAILED_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FAILED) - - if(${CXX_FLAG_FOUND} EQUAL -1 AND ${CXX_FLAG_FAILED} EQUAL -1) - unset(CXX_FLAG_SUPPORTED CACHE) - message("Checking CXX compiler flag support for: " ${cxx_flag}) - check_cxx_compiler_flag("${cxx_flag}" CXX_FLAG_SUPPORTED) - if(${CXX_FLAG_SUPPORTED}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxx_flag}" CACHE STRING "") - else() - set(DRACO_FAILED_CXX_FLAGS - "${DRACO_FAILED_CXX_FLAGS} ${cxx_flag}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Convenience method for adding a flag to both the C and C++ compiler command -# lines. -macro(add_compiler_flag_if_supported flag) - add_c_flag_if_supported(${flag}) - add_cxx_flag_if_supported(${flag}) -endmacro() - -# Checks C compiler for support of $c_flag and terminates generation when -# support is not present. -macro(require_c_flag c_flag update_c_flags) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${c_flag}" C_FLAG_FOUND) - - if(${C_FLAG_FOUND} EQUAL -1) - unset(HAVE_C_FLAG CACHE) - message("Checking C compiler flag support for: " ${c_flag}) - check_c_compiler_flag("${c_flag}" HAVE_C_FLAG) - if(NOT ${HAVE_C_FLAG}) - message( - FATAL_ERROR "${PROJECT_NAME} requires support for C flag: ${c_flag}.") - endif() - if(${update_c_flags}) - set(CMAKE_C_FLAGS "${c_flag} ${CMAKE_C_FLAGS}" CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks CXX compiler for support of $cxx_flag and terminates generation when -# support is not present. -macro(require_cxx_flag cxx_flag update_cxx_flags) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) - - if(${CXX_FLAG_FOUND} EQUAL -1) - unset(HAVE_CXX_FLAG CACHE) - message("Checking CXX compiler flag support for: " ${cxx_flag}) - check_cxx_compiler_flag("${cxx_flag}" HAVE_CXX_FLAG) - if(NOT ${HAVE_CXX_FLAG}) - message( - FATAL_ERROR - "${PROJECT_NAME} requires support for CXX flag: ${cxx_flag}.") - endif() - if(${update_cxx_flags}) - set(CMAKE_CXX_FLAGS - "${cxx_flag} ${CMAKE_CXX_FLAGS}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks for support of $flag by both the C and CXX compilers. Terminates -# generation when support is not present in both compilers. -macro(require_compiler_flag flag update_cmake_flags) - require_c_flag(${flag} ${update_cmake_flags}) - require_cxx_flag(${flag} ${update_cmake_flags}) -endmacro() - -# Checks only non-MSVC targets for support of $c_flag and terminates generation -# when support is not present. -macro(require_c_flag_nomsvc c_flag update_c_flags) - if(NOT MSVC) - require_c_flag(${c_flag} ${update_c_flags}) - endif() -endmacro() - -# Checks only non-MSVC targets for support of $cxx_flag and terminates -# generation when support is not present. -macro(require_cxx_flag_nomsvc cxx_flag update_cxx_flags) - if(NOT MSVC) - require_cxx_flag(${cxx_flag} ${update_cxx_flags}) - endif() -endmacro() - -# Checks only non-MSVC targets for support of $flag by both the C and CXX -# compilers. Terminates generation when support is not present in both -# compilers. -macro(require_compiler_flag_nomsvc flag update_cmake_flags) - require_c_flag_nomsvc(${flag} ${update_cmake_flags}) - require_cxx_flag_nomsvc(${flag} ${update_cmake_flags}) -endmacro() - -# Adds $flag to assembler command line. -macro(append_as_flag flag) - unset(AS_FLAG_FOUND CACHE) - string(FIND "${DRACO_AS_FLAGS}" "${flag}" AS_FLAG_FOUND) - - if(${AS_FLAG_FOUND} EQUAL -1) - set(DRACO_AS_FLAGS "${DRACO_AS_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the C compiler command line. -macro(append_c_flag flag) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${flag}" C_FLAG_FOUND) - - if(${C_FLAG_FOUND} EQUAL -1) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the CXX compiler command line. -macro(append_cxx_flag flag) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" CXX_FLAG_FOUND) - - if(${CXX_FLAG_FOUND} EQUAL -1) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the C and CXX compiler command lines. -macro(append_compiler_flag flag) - append_c_flag(${flag}) - append_cxx_flag(${flag}) -endmacro() - -# Adds $flag to the executable linker command line. -macro(append_exe_linker_flag flag) - unset(LINKER_FLAG_FOUND CACHE) - string(FIND "${CMAKE_EXE_LINKER_FLAGS}" "${flag}" LINKER_FLAG_FOUND) - - if(${LINKER_FLAG_FOUND} EQUAL -1) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the link flags for $target. -function(append_link_flag_to_target target flags) - unset(target_link_flags) - get_target_property(target_link_flags ${target} LINK_FLAGS) - - if(target_link_flags) - unset(link_flag_found) - string(FIND "${target_link_flags}" "${flags}" link_flag_found) - - if(NOT ${link_flag_found} EQUAL -1) - return() - endif() - - set(target_link_flags "${target_link_flags} ${flags}") - else() - set(target_link_flags "${flags}") - endif() - - set_target_properties(${target} PROPERTIES LINK_FLAGS ${target_link_flags}) -endfunction() - -# Adds $flag to executable linker flags, and makes sure C/CXX builds still work. -macro(require_linker_flag flag) - append_exe_linker_flag(${flag}) - - unset(c_passed) - draco_check_c_compiles("LINKER_FLAG_C_TEST(${flag})" "" c_passed) - unset(cxx_passed) - draco_check_cxx_compiles("LINKER_FLAG_CXX_TEST(${flag})" "" cxx_passed) - - if(NOT c_passed OR NOT cxx_passed) - message(FATAL_ERROR "Linker flag test for ${flag} failed.") - endif() -endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/compiler_tests.cmake b/Engine/lib/assimp/contrib/draco/cmake/compiler_tests.cmake deleted file mode 100644 index e781a6537..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/compiler_tests.cmake +++ /dev/null @@ -1,103 +0,0 @@ -if(DRACO_CMAKE_COMPILER_TESTS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_COMPILER_TESTS_CMAKE_ 1) - -include(CheckCSourceCompiles) -include(CheckCXXSourceCompiles) - -# The basic main() macro used in all compile tests. -set(DRACO_C_MAIN "\nint main(void) { return 0; }") -set(DRACO_CXX_MAIN "\nint main() { return 0; }") - -# Strings containing the names of passed and failed tests. -set(DRACO_C_PASSED_TESTS) -set(DRACO_C_FAILED_TESTS) -set(DRACO_CXX_PASSED_TESTS) -set(DRACO_CXX_FAILED_TESTS) - -macro(draco_push_var var new_value) - set(SAVED_${var} ${var}) - set(${var} ${new_value}) -endmacro() - -macro(draco_pop_var var) - set(var ${SAVED_${var}}) - unset(SAVED_${var}) -endmacro() - -# Confirms $test_source compiles and stores $test_name in one of -# $DRACO_C_PASSED_TESTS or $DRACO_C_FAILED_TESTS depending on out come. When the -# test passes $result_var is set to 1. When it fails $result_var is unset. The -# test is not run if the test name is found in either of the passed or failed -# test variables. -macro(draco_check_c_compiles test_name test_source result_var) - unset(C_TEST_PASSED CACHE) - unset(C_TEST_FAILED CACHE) - string(FIND "${DRACO_C_PASSED_TESTS}" "${test_name}" C_TEST_PASSED) - string(FIND "${DRACO_C_FAILED_TESTS}" "${test_name}" C_TEST_FAILED) - if(${C_TEST_PASSED} EQUAL -1 AND ${C_TEST_FAILED} EQUAL -1) - unset(C_TEST_COMPILED CACHE) - message("Running C compiler test: ${test_name}") - check_c_source_compiles("${test_source} ${DRACO_C_MAIN}" C_TEST_COMPILED) - set(${result_var} ${C_TEST_COMPILED}) - - if(${C_TEST_COMPILED}) - set(DRACO_C_PASSED_TESTS "${DRACO_C_PASSED_TESTS} ${test_name}") - else() - set(DRACO_C_FAILED_TESTS "${DRACO_C_FAILED_TESTS} ${test_name}") - message("C Compiler test ${test_name} failed.") - endif() - elseif(NOT ${C_TEST_PASSED} EQUAL -1) - set(${result_var} 1) - else() # ${C_TEST_FAILED} NOT EQUAL -1 - unset(${result_var}) - endif() -endmacro() - -# Confirms $test_source compiles and stores $test_name in one of -# $DRACO_CXX_PASSED_TESTS or $DRACO_CXX_FAILED_TESTS depending on out come. When -# the test passes $result_var is set to 1. When it fails $result_var is unset. -# The test is not run if the test name is found in either of the passed or -# failed test variables. -macro(draco_check_cxx_compiles test_name test_source result_var) - unset(CXX_TEST_PASSED CACHE) - unset(CXX_TEST_FAILED CACHE) - string(FIND "${DRACO_CXX_PASSED_TESTS}" "${test_name}" CXX_TEST_PASSED) - string(FIND "${DRACO_CXX_FAILED_TESTS}" "${test_name}" CXX_TEST_FAILED) - if(${CXX_TEST_PASSED} EQUAL -1 AND ${CXX_TEST_FAILED} EQUAL -1) - unset(CXX_TEST_COMPILED CACHE) - message("Running CXX compiler test: ${test_name}") - check_cxx_source_compiles("${test_source} ${DRACO_CXX_MAIN}" - CXX_TEST_COMPILED) - set(${result_var} ${CXX_TEST_COMPILED}) - - if(${CXX_TEST_COMPILED}) - set(DRACO_CXX_PASSED_TESTS "${DRACO_CXX_PASSED_TESTS} ${test_name}") - else() - set(DRACO_CXX_FAILED_TESTS "${DRACO_CXX_FAILED_TESTS} ${test_name}") - message("CXX Compiler test ${test_name} failed.") - endif() - elseif(NOT ${CXX_TEST_PASSED} EQUAL -1) - set(${result_var} 1) - else() # ${CXX_TEST_FAILED} NOT EQUAL -1 - unset(${result_var}) - endif() -endmacro() - -# Convenience macro that confirms $test_source compiles as C and C++. -# $result_var is set to 1 when both tests are successful, and 0 when one or both -# tests fail. Note: This macro is intended to be used to write to result -# variables that are expanded via configure_file(). $result_var is set to 1 or 0 -# to allow direct usage of the value in generated source files. -macro(draco_check_source_compiles test_name test_source result_var) - unset(C_PASSED) - unset(CXX_PASSED) - draco_check_c_compiles(${test_name} ${test_source} C_PASSED) - draco_check_cxx_compiles(${test_name} ${test_source} CXX_PASSED) - if(${C_PASSED} AND ${CXX_PASSED}) - set(${result_var} 1) - else() - set(${result_var} 0) - endif() -endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco-config.cmake.template b/Engine/lib/assimp/contrib/draco/cmake/draco-config.cmake.template index ca4a456bf..ed86823ea 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco-config.cmake.template +++ b/Engine/lib/assimp/contrib/draco/cmake/draco-config.cmake.template @@ -1,2 +1,3 @@ -set(DRACO_INCLUDE_DIRS "@DRACO_INCLUDE_DIRS@") -set(DRACO_LIBRARIES "draco") +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/draco-targets.cmake") diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco.pc.template b/Engine/lib/assimp/contrib/draco/cmake/draco.pc.template index b8ae48212..050219ccb 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco.pc.template +++ b/Engine/lib/assimp/contrib/draco/cmake/draco.pc.template @@ -1,11 +1,6 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - Name: @PROJECT_NAME@ Description: Draco geometry de(com)pression library. Version: @DRACO_VERSION@ -Cflags: -I${includedir} -Libs: -L${libdir} -ldraco +Cflags: -I@includes_path@ +Libs: -L@libs_path@ -ldraco Libs.private: @CMAKE_THREAD_LIBS_INIT@ diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_build_definitions.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_build_definitions.cmake index f7354c15f..4dc232333 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_build_definitions.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_build_definitions.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_ @@ -17,10 +31,6 @@ macro(set_draco_target) endif() set(draco_plugin_dependency draco_static) endif() - - if(BUILD_SHARED_LIBS) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - endif() endmacro() # Configures flags and sets build system globals. @@ -36,23 +46,37 @@ macro(draco_set_build_definitions) endif() draco_load_version_info() - set(DRACO_SOVERSION 1) + + # Library version info. See the libtool docs for updating the values: + # https://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info + # + # c=, r=, a= + # + # libtool generates a .so file as .so.[c-a].a.r, while -version-info c:r:a is + # passed to libtool. + # + # We set DRACO_SOVERSION = [c-a].a.r + set(LT_CURRENT 8) + set(LT_REVISION 0) + set(LT_AGE 0) + math(EXPR DRACO_SOVERSION_MAJOR "${LT_CURRENT} - ${LT_AGE}") + set(DRACO_SOVERSION "${DRACO_SOVERSION_MAJOR}.${LT_AGE}.${LT_REVISION}") + unset(LT_CURRENT) + unset(LT_REVISION) + unset(LT_AGE) list(APPEND draco_include_paths "${draco_root}" "${draco_root}/src" "${draco_build}") - if(DRACO_ABSL) - list(APPEND draco_include_path "${draco_root}/third_party/abseil-cpp") + if(DRACO_TRANSCODER_SUPPORTED) + draco_setup_eigen() + draco_setup_filesystem() + draco_setup_tinygltf() + + endif() - list(APPEND draco_gtest_include_paths - "${draco_root}/../googletest/googlemock/include" - "${draco_root}/../googletest/googlemock" - "${draco_root}/../googletest/googletest/include" - "${draco_root}/../googletest/googletest") - list(APPEND draco_test_include_paths ${draco_include_paths} - ${draco_gtest_include_paths}) list(APPEND draco_defines "DRACO_CMAKE=1" "DRACO_FLAGS_SRCDIR=\"${draco_root}\"" "DRACO_FLAGS_TMPDIR=\"/tmp\"") @@ -63,11 +87,22 @@ macro(draco_set_build_definitions) if(BUILD_SHARED_LIBS) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() - else() + endif() + + if(NOT MSVC) if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) # Ensure 64-bit platforms can support large files. list(APPEND draco_defines "_LARGEFILE_SOURCE" "_FILE_OFFSET_BITS=64") endif() + + if(NOT DRACO_DEBUG_COMPILER_WARNINGS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + list(APPEND draco_clang_cxx_flags + "-Wno-implicit-const-int-float-conversion") + else() + list(APPEND draco_base_cxx_flags "-Wno-deprecated-declarations") + endif() + endif() endif() if(ANDROID) @@ -102,13 +137,9 @@ macro(draco_set_build_definitions) set(draco_neon_source_file_suffix "neon.cc") set(draco_sse4_source_file_suffix "sse4.cc") - if((${CMAKE_CXX_COMPILER_ID} - STREQUAL - "GNU" - AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 5) - OR (${CMAKE_CXX_COMPILER_ID} - STREQUAL - "Clang" + if((${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${CMAKE_CXX_COMPILER_VERSION} + VERSION_LESS 5) + OR (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4)) message( WARNING "GNU/GCC < v5 or Clang/LLVM < v4, ENABLING COMPATIBILITY MODE.") @@ -117,7 +148,9 @@ macro(draco_set_build_definitions) if(EMSCRIPTEN) draco_check_emscripten_environment() - draco_get_required_emscripten_flags(FLAG_LIST_VAR draco_base_cxx_flags) + draco_get_required_emscripten_flags( + FLAG_LIST_VAR_COMPILER draco_base_cxx_flags + FLAG_LIST_VAR_LINKER draco_base_exe_linker_flags) endif() draco_configure_sanitizer() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_cpu_detection.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_cpu_detection.cmake index 96e4a289b..c3b77b80c 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_cpu_detection.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_cpu_detection.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_CPU_DETECTION_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_CPU_DETECTION_CMAKE_ diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_dependencies.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_dependencies.cmake new file mode 100644 index 000000000..91ee0839b --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_dependencies.cmake @@ -0,0 +1,136 @@ +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +if(DRACO_CMAKE_DRACO_DEPENDENCIES_CMAKE) + return() +endif() +set(DRACO_CMAKE_DRACO_DEPENDENCIES_CMAKE 1) + +include("${draco_root}/cmake/draco_variables.cmake") + +# Each variable holds a user specified custom path to a local copy of the +# sources that belong to each project that Draco depends on. When paths are +# empty the build will be generated pointing to the Draco git submodules. +# Otherwise the paths specified by the user will be used in the build +# configuration. + +# Path to the Eigen. The path must contain the Eigen directory. +set(DRACO_EIGEN_PATH) +draco_track_configuration_variable(DRACO_EIGEN_PATH) + +# Path to the gulrak/filesystem installation. The path specified must contain +# the ghc subdirectory that houses the filesystem includes. +set(DRACO_FILESYSTEM_PATH) +draco_track_configuration_variable(DRACO_FILESYSTEM_PATH) + +# Path to the googletest installation. The path must be to the root of the +# Googletest project directory. +set(DRACO_GOOGLETEST_PATH) +draco_track_configuration_variable(DRACO_GOOGLETEST_PATH) + +# Path to the syoyo/tinygltf installation. The path must be to the root of the +# project directory. +set(DRACO_TINYGLTF_PATH) +draco_track_configuration_variable(DRACO_TINYGLTF_PATH) + +# Utility macro for killing the build due to a missing submodule directory. +macro(draco_die_missing_submodule dir) + message(FATAL_ERROR "${dir} missing, run git submodule update --init") +endmacro() + +# Determines the Eigen location and updates the build configuration accordingly. +macro(draco_setup_eigen) + if(DRACO_EIGEN_PATH) + set(eigen_path "${DRACO_EIGEN_PATH}") + + if(NOT IS_DIRECTORY "${eigen_path}") + message(FATAL_ERROR "DRACO_EIGEN_PATH does not exist.") + endif() + else() + set(eigen_path "${draco_root}/third_party/eigen") + + if(NOT IS_DIRECTORY "${eigen_path}") + draco_die_missing_submodule("${eigen_path}") + endif() + endif() + + set(eigen_include_path "${eigen_path}/Eigen") + + if(NOT EXISTS "${eigen_path}/Eigen") + message(FATAL_ERROR "The eigen path does not contain an Eigen directory.") + endif() + + list(APPEND draco_include_paths "${eigen_path}") +endmacro() + +# Determines the gulrak/filesystem location and updates the build configuration +# accordingly. +macro(draco_setup_filesystem) + if(DRACO_FILESYSTEM_PATH) + set(fs_path "${DRACO_FILESYSTEM_PATH}") + + if(NOT IS_DIRECTORY "${fs_path}") + message(FATAL_ERROR "DRACO_FILESYSTEM_PATH does not exist.") + endif() + else() + set(fs_path "${draco_root}/third_party/filesystem/include") + + if(NOT IS_DIRECTORY "${fs_path}") + draco_die_missing_submodule("${fs_path}") + endif() + endif() + + list(APPEND draco_include_paths "${fs_path}") +endmacro() + +# Determines the Googletest location and sets up include and source list vars +# for the draco_tests build. +macro(draco_setup_googletest) + if(DRACO_GOOGLETEST_PATH) + set(gtest_path "${DRACO_GOOGLETEST_PATH}") + if(NOT IS_DIRECTORY "${gtest_path}") + message(FATAL_ERROR "DRACO_GOOGLETEST_PATH does not exist.") + endif() + else() + set(gtest_path "${draco_root}/third_party/googletest") + endif() + + list(APPEND draco_test_include_paths ${draco_include_paths} + "${gtest_path}/include" "${gtest_path}/googlemock" + "${gtest_path}/googletest/include" "${gtest_path}/googletest") + + list(APPEND draco_gtest_all "${gtest_path}/googletest/src/gtest-all.cc") + list(APPEND draco_gtest_main "${gtest_path}/googletest/src/gtest_main.cc") +endmacro() + + +# Determines the location of TinyGLTF and updates the build configuration +# accordingly. +macro(draco_setup_tinygltf) + if(DRACO_TINYGLTF_PATH) + set(tinygltf_path "${DRACO_TINYGLTF_PATH}") + + if(NOT IS_DIRECTORY "${tinygltf_path}") + message(FATAL_ERROR "DRACO_TINYGLTF_PATH does not exist.") + endif() + else() + set(tinygltf_path "${draco_root}/third_party/tinygltf") + + if(NOT IS_DIRECTORY "${tinygltf_path}") + draco_die_missing_submodule("${tinygltf_path}") + endif() + endif() + + list(APPEND draco_include_paths "${tinygltf_path}") +endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_emscripten.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_emscripten.cmake index 10c935043..c9616ae86 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_emscripten.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_emscripten.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_EMSCRIPTEN_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_EMSCRIPTEN_CMAKE_ @@ -18,39 +32,64 @@ endmacro() # Obtains the required Emscripten flags for Draco targets. macro(draco_get_required_emscripten_flags) - set(em_FLAG_LIST_VAR) + set(em_FLAG_LIST_VAR_COMPILER) + set(em_FLAG_LIST_VAR_LINKER) set(em_flags) - set(em_single_arg_opts FLAG_LIST_VAR) + set(em_single_arg_opts FLAG_LIST_VAR_COMPILER FLAG_LIST_VAR_LINKER) set(em_multi_arg_opts) cmake_parse_arguments(em "${em_flags}" "${em_single_arg_opts}" "${em_multi_arg_opts}" ${ARGN}) - if(NOT em_FLAG_LIST_VAR) - message(FATAL "draco_get_required_emscripten_flags: FLAG_LIST_VAR required") + if(NOT em_FLAG_LIST_VAR_COMPILER) + message( + FATAL + "draco_get_required_emscripten_flags: FLAG_LIST_VAR_COMPILER required") + endif() + + if(NOT em_FLAG_LIST_VAR_LINKER) + message( + FATAL + "draco_get_required_emscripten_flags: FLAG_LIST_VAR_LINKER required") endif() if(DRACO_JS_GLUE) unset(required_flags) - list(APPEND ${em_FLAG_LIST_VAR} "-sALLOW_MEMORY_GROWTH=1") - list(APPEND ${em_FLAG_LIST_VAR} "-Wno-almost-asm") - list(APPEND ${em_FLAG_LIST_VAR} "--memory-init-file" "0") - list(APPEND ${em_FLAG_LIST_VAR} "-fno-omit-frame-pointer") - list(APPEND ${em_FLAG_LIST_VAR} "-sMODULARIZE=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sNO_FILESYSTEM=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sEXPORTED_RUNTIME_METHODS=[]") - list(APPEND ${em_FLAG_LIST_VAR} "-sPRECISE_F32=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sNODEJS_CATCH_EXIT=0") - list(APPEND ${em_FLAG_LIST_VAR} "-sNODEJS_CATCH_REJECTION=0") + # TODO(tomfinegan): Revisit splitting of compile/link flags for Emscripten, + # and drop -Wno-unused-command-line-argument. Emscripten complains about + # what are supposedly link-only flags sent with compile commands, but then + # proceeds to produce broken code if the warnings are heeded. + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} + "-Wno-unused-command-line-argument") + + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-Wno-almost-asm") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "--memory-init-file" "0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-fno-omit-frame-pointer") + + # According to Emscripten the following flags are linker only, but sending + # these flags (en masse) to only the linker results in a broken Emscripten + # build with an empty DracoDecoderModule. + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sALLOW_MEMORY_GROWTH=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sMODULARIZE=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sFILESYSTEM=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} + "-sEXPORTED_FUNCTIONS=[\"_free\",\"_malloc\"]") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sPRECISE_F32=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sNODEJS_CATCH_EXIT=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sNODEJS_CATCH_REJECTION=0") if(DRACO_FAST) - list(APPEND ${em_FLAG_LIST_VAR} "--llvm-lto" "1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "--llvm-lto" "1") endif() + + # The WASM flag is reported as linker only. if(DRACO_WASM) - list(APPEND ${em_FLAG_LIST_VAR} "-sWASM=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sWASM=1") else() - list(APPEND ${em_FLAG_LIST_VAR} "-sWASM=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sWASM=0") endif() + + # The LEGACY_VM_SUPPORT flag is reported as linker only. if(DRACO_IE_COMPATIBLE) - list(APPEND ${em_FLAG_LIST_VAR} "-sLEGACY_VM_SUPPORT=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sLEGACY_VM_SUPPORT=1") endif() endif() endmacro() @@ -66,10 +105,11 @@ macro(draco_generate_emscripten_glue) "${glue_multi_arg_opts}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_generate_emscripten_glue -----------\n" - "glue_INPUT_IDL=${glue_INPUT_IDL}\n" - "glue_OUTPUT_PATH=${glue_OUTPUT_PATH}\n" ] - "----------------------------------------------------\n") + message( + "--------- draco_generate_emscripten_glue -----------\n" + "glue_INPUT_IDL=${glue_INPUT_IDL}\n" + "glue_OUTPUT_PATH=${glue_OUTPUT_PATH}\n" + "----------------------------------------------------\n") endif() if(NOT glue_INPUT_IDL OR NOT glue_OUTPUT_PATH) @@ -79,22 +119,22 @@ macro(draco_generate_emscripten_glue) endif() # Generate the glue source. - execute_process(COMMAND ${PYTHON_EXECUTABLE} - $ENV{EMSCRIPTEN}/tools/webidl_binder.py - ${glue_INPUT_IDL} ${glue_OUTPUT_PATH}) + execute_process( + COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + ${glue_INPUT_IDL} ${glue_OUTPUT_PATH}) if(NOT EXISTS "${glue_OUTPUT_PATH}.cpp") message(FATAL_ERROR "JS glue generation failed for ${glue_INPUT_IDL}.") endif() # Create a dependency so that it regenerated on edits. - add_custom_command(OUTPUT "${glue_OUTPUT_PATH}.cpp" - COMMAND ${PYTHON_EXECUTABLE} - $ENV{EMSCRIPTEN}/tools/webidl_binder.py - ${glue_INPUT_IDL} ${glue_OUTPUT_PATH} - DEPENDS ${draco_js_dec_idl} - COMMENT "Generating ${glue_OUTPUT_PATH}.cpp." - WORKING_DIRECTORY ${draco_build} - VERBATIM) + add_custom_command( + OUTPUT "${glue_OUTPUT_PATH}.cpp" + COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + ${glue_INPUT_IDL} ${glue_OUTPUT_PATH} + DEPENDS ${draco_js_dec_idl} + COMMENT "Generating ${glue_OUTPUT_PATH}.cpp." + WORKING_DIRECTORY ${draco_build} + VERBATIM) endmacro() # Wrapper for draco_add_executable() that handles the extra work necessary for @@ -120,8 +160,14 @@ macro(draco_add_emscripten_executable) unset(emexe_LINK_FLAGS) set(optional_args) set(single_value_args NAME GLUE_PATH) - set(multi_value_args SOURCES DEFINES FEATURES INCLUDES LINK_FLAGS - PRE_LINK_JS_SOURCES POST_LINK_JS_SOURCES) + set(multi_value_args + SOURCES + DEFINES + FEATURES + INCLUDES + LINK_FLAGS + PRE_LINK_JS_SOURCES + POST_LINK_JS_SOURCES) cmake_parse_arguments(emexe "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) @@ -136,49 +182,50 @@ macro(draco_add_emscripten_executable) endif() if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_emscripten_executable ---------\n" - "emexe_NAME=${emexe_NAME}\n" - "emexe_SOURCES=${emexe_SOURCES}\n" - "emexe_DEFINES=${emexe_DEFINES}\n" - "emexe_INCLUDES=${emexe_INCLUDES}\n" - "emexe_LINK_FLAGS=${emexe_LINK_FLAGS}\n" - "emexe_GLUE_PATH=${emexe_GLUE_PATH}\n" - "emexe_FEATURES=${emexe_FEATURES}\n" - "emexe_PRE_LINK_JS_SOURCES=${emexe_PRE_LINK_JS_SOURCES}\n" - "emexe_POST_LINK_JS_SOURCES=${emexe_POST_LINK_JS_SOURCES}\n" - "----------------------------------------------------\n") + message( + "--------- draco_add_emscripten_executable ---------\n" + "emexe_NAME=${emexe_NAME}\n" + "emexe_SOURCES=${emexe_SOURCES}\n" + "emexe_DEFINES=${emexe_DEFINES}\n" + "emexe_INCLUDES=${emexe_INCLUDES}\n" + "emexe_LINK_FLAGS=${emexe_LINK_FLAGS}\n" + "emexe_GLUE_PATH=${emexe_GLUE_PATH}\n" + "emexe_FEATURES=${emexe_FEATURES}\n" + "emexe_PRE_LINK_JS_SOURCES=${emexe_PRE_LINK_JS_SOURCES}\n" + "emexe_POST_LINK_JS_SOURCES=${emexe_POST_LINK_JS_SOURCES}\n" + "----------------------------------------------------\n") endif() # The Emscripten linker needs the C++ flags in addition to whatever has been # passed in with the target. list(APPEND emexe_LINK_FLAGS ${DRACO_CXX_FLAGS}) - if(DRACO_GLTF) - draco_add_executable(NAME - ${emexe_NAME} - OUTPUT_NAME - ${emexe_NAME}_gltf - SOURCES - ${emexe_SOURCES} - DEFINES - ${emexe_DEFINES} - INCLUDES - ${emexe_INCLUDES} - LINK_FLAGS - ${emexe_LINK_FLAGS}) + if(DRACO_GLTF_BITSTREAM) + # Add "_gltf" suffix to target output name. + draco_add_executable( + NAME ${emexe_NAME} + OUTPUT_NAME ${emexe_NAME}_gltf + SOURCES ${emexe_SOURCES} + DEFINES ${emexe_DEFINES} + INCLUDES ${emexe_INCLUDES} + LINK_FLAGS ${emexe_LINK_FLAGS}) else() - draco_add_executable(NAME ${emexe_NAME} SOURCES ${emexe_SOURCES} DEFINES - ${emexe_DEFINES} INCLUDES ${emexe_INCLUDES} LINK_FLAGS - ${emexe_LINK_FLAGS}) + draco_add_executable( + NAME ${emexe_NAME} + SOURCES ${emexe_SOURCES} + DEFINES ${emexe_DEFINES} + INCLUDES ${emexe_INCLUDES} + LINK_FLAGS ${emexe_LINK_FLAGS}) endif() foreach(feature ${emexe_FEATURES}) draco_enable_feature(FEATURE ${feature} TARGETS ${emexe_NAME}) endforeach() - set_property(SOURCE ${emexe_SOURCES} - APPEND - PROPERTY OBJECT_DEPENDS "${emexe_GLUE_PATH}.cpp") + set_property( + SOURCE ${emexe_SOURCES} + APPEND + PROPERTY OBJECT_DEPENDS "${emexe_GLUE_PATH}.cpp") em_link_pre_js(${emexe_NAME} ${emexe_PRE_LINK_JS_SOURCES}) em_link_post_js(${emexe_NAME} "${emexe_GLUE_PATH}.js" ${emexe_POST_LINK_JS_SOURCES}) diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_flags.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_flags.cmake index 0397859a4..f3b24c6e1 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_flags.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_flags.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_FLAGS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_FLAGS_CMAKE_ @@ -24,7 +38,7 @@ macro(draco_set_compiler_flags_for_sources) endif() set_source_files_properties(${compiler_SOURCES} PROPERTIES COMPILE_FLAGS - ${compiler_FLAGS}) + ${compiler_FLAGS}) if(DRACO_VERBOSE GREATER 1) foreach(source ${compiler_SOURCES}) @@ -85,8 +99,8 @@ macro(draco_test_cxx_flag) # are passed as a list it will remove the list separators, and attempt to run # a compile command using list entries concatenated together as a single # argument. Avoid the problem by forcing the argument to be a string. - draco_set_and_stringify(SOURCE_VARS all_cxx_flags DEST all_cxx_flags) - check_cxx_compiler_flag("${all_cxx_flags}" draco_all_cxx_flags_pass) + draco_set_and_stringify(SOURCE_VARS all_cxx_flags DEST all_cxx_flags_string) + check_cxx_compiler_flag("${all_cxx_flags_string}" draco_all_cxx_flags_pass) if(cxx_test_FLAG_REQUIRED AND NOT draco_all_cxx_flags_pass) draco_die("Flag test failed for required flag(s): " @@ -245,3 +259,34 @@ macro(draco_set_cxx_flags) draco_test_cxx_flag(FLAG_LIST_VAR_NAMES ${cxx_flag_lists}) endif() endmacro() + +# Collects Draco built-in and user-specified linker flags and tests them. Halts +# configuration and reports the error when any flags cause the build to fail. +# +# Note: draco_test_exe_linker_flag() does the real work of setting the flags and +# running the test compile commands. +macro(draco_set_exe_linker_flags) + unset(linker_flag_lists) + + if(DRACO_VERBOSE) + message("draco_set_exe_linker_flags: " + "draco_base_exe_linker_flags=${draco_base_exe_linker_flags}") + endif() + + if(draco_base_exe_linker_flags) + list(APPEND linker_flag_lists draco_base_exe_linker_flags) + endif() + + if(linker_flag_lists) + unset(test_linker_flags) + + if(DRACO_VERBOSE) + message("draco_set_exe_linker_flags: " + "linker_flag_lists=${linker_flag_lists}") + endif() + + draco_set_and_stringify(DEST test_linker_flags SOURCE_VARS + ${linker_flag_lists}) + draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME test_linker_flags) + endif() +endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_helpers.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_helpers.cmake index 0b3b804cf..69e24c5be 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_helpers.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_helpers.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_HELPERS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_HELPERS_CMAKE_ diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_install.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_install.cmake index 09bfb591d..3be1ba163 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_install.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_install.cmake @@ -1,32 +1,32 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_INSTALL_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_INSTALL_CMAKE_ set(DRACO_CMAKE_DRACO_INSTALL_CMAKE_ 1) +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + # Sets up the draco install targets. Must be called after the static library # target is created. macro(draco_setup_install_target) - include(GNUInstallDirs) - - # pkg-config: draco.pc - set(prefix "${CMAKE_INSTALL_PREFIX}") - set(exec_prefix "\${prefix}") - set(libdir "\${prefix}/${CMAKE_INSTALL_LIBDIR}") - set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") - set(draco_lib_name "draco") - - configure_file("${draco_root}/cmake/draco.pc.template" - "${draco_build}/draco.pc" @ONLY NEWLINE_STYLE UNIX) - install(FILES "${draco_build}/draco.pc" - DESTINATION "${prefix}/${CMAKE_INSTALL_LIBDIR}/pkgconfig") - - # CMake config: draco-config.cmake - set(DRACO_INCLUDE_DIRS "${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") - configure_file("${draco_root}/cmake/draco-config.cmake.template" - "${draco_build}/draco-config.cmake" @ONLY NEWLINE_STYLE UNIX) - install( - FILES "${draco_build}/draco-config.cmake" - DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/cmake") + set(bin_path "${CMAKE_INSTALL_BINDIR}") + set(data_path "${CMAKE_INSTALL_DATAROOTDIR}") + set(includes_path "${CMAKE_INSTALL_INCLUDEDIR}") + set(libs_path "${CMAKE_INSTALL_LIBDIR}") foreach(file ${draco_sources}) if(file MATCHES "h$") @@ -34,46 +34,88 @@ macro(draco_setup_install_target) endif() endforeach() + list(REMOVE_DUPLICATES draco_api_includes) + # Strip $draco_src_root from the file paths: we need to install relative to # $include_directory. list(TRANSFORM draco_api_includes REPLACE "${draco_src_root}/" "") - set(include_directory "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") foreach(draco_api_include ${draco_api_includes}) get_filename_component(file_directory ${draco_api_include} DIRECTORY) - set(target_directory "${include_directory}/draco/${file_directory}") + set(target_directory "${includes_path}/draco/${file_directory}") install(FILES ${draco_src_root}/${draco_api_include} DESTINATION "${target_directory}") endforeach() - install( - FILES "${draco_build}/draco/draco_features.h" - DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/draco/") + install(FILES "${draco_build}/draco/draco_features.h" + DESTINATION "${includes_path}/draco/") - install(TARGETS draco_decoder DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - install(TARGETS draco_encoder DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + install(TARGETS draco_decoder DESTINATION "${bin_path}") + install(TARGETS draco_encoder DESTINATION "${bin_path}") + + if(DRACO_TRANSCODER_SUPPORTED) + install(TARGETS draco_transcoder DESTINATION "${bin_path}") + endif() if(MSVC) - install(TARGETS draco DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco + EXPORT dracoExport + RUNTIME DESTINATION "${bin_path}" + ARCHIVE DESTINATION "${libs_path}" + LIBRARY DESTINATION "${libs_path}") else() - install(TARGETS draco_static DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco_static + EXPORT dracoExport + DESTINATION "${libs_path}") + if(BUILD_SHARED_LIBS) - install(TARGETS draco_shared DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco_shared + EXPORT dracoExport + RUNTIME DESTINATION "${bin_path}" + ARCHIVE DESTINATION "${libs_path}" + LIBRARY DESTINATION "${libs_path}") endif() endif() if(DRACO_UNITY_PLUGIN) - install(TARGETS dracodec_unity DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - endif() - if(DRACO_MAYA_PLUGIN) - install(TARGETS draco_maya_wrapper DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install(TARGETS dracodec_unity DESTINATION "${libs_path}") endif() + if(DRACO_MAYA_PLUGIN) + install(TARGETS draco_maya_wrapper DESTINATION "${libs_path}") + endif() + + # pkg-config: draco.pc + configure_file("${draco_root}/cmake/draco.pc.template" + "${draco_build}/draco.pc" @ONLY NEWLINE_STYLE UNIX) + install(FILES "${draco_build}/draco.pc" DESTINATION "${libs_path}/pkgconfig") + + # CMake config: draco-config.cmake + configure_package_config_file( + "${draco_root}/cmake/draco-config.cmake.template" + "${draco_build}/draco-config.cmake" + INSTALL_DESTINATION "${data_path}/cmake/draco") + + write_basic_package_version_file( + "${draco_build}/draco-config-version.cmake" + VERSION ${DRACO_VERSION} + COMPATIBILITY AnyNewerVersion) + + export( + EXPORT dracoExport + NAMESPACE draco:: + FILE "${draco_build}/draco-targets.cmake") + + install( + EXPORT dracoExport + NAMESPACE draco:: + FILE draco-targets.cmake + DESTINATION "${data_path}/cmake/draco") + + install(FILES "${draco_build}/draco-config.cmake" + "${draco_build}/draco-config-version.cmake" + DESTINATION "${data_path}/cmake/draco") endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_intrinsics.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_intrinsics.cmake index 9011c0de5..178df97a6 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_intrinsics.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_intrinsics.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_INTRINSICS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_INTRINSICS_CMAKE_ @@ -61,17 +75,15 @@ macro(draco_process_intrinsics_sources) unset(sse4_sources) list(APPEND sse4_sources ${arg_SOURCES}) - list(FILTER sse4_sources INCLUDE REGEX - "${draco_sse4_source_file_suffix}$") + list(FILTER sse4_sources INCLUDE REGEX "${draco_sse4_source_file_suffix}$") if(sse4_sources) unset(sse4_flags) - draco_get_intrinsics_flag_for_suffix(SUFFIX - ${draco_sse4_source_file_suffix} - VARIABLE sse4_flags) + draco_get_intrinsics_flag_for_suffix( + SUFFIX ${draco_sse4_source_file_suffix} VARIABLE sse4_flags) if(sse4_flags) draco_set_compiler_flags_for_sources(SOURCES ${sse4_sources} FLAGS - ${sse4_flags}) + ${sse4_flags}) endif() endif() endif() @@ -79,17 +91,15 @@ macro(draco_process_intrinsics_sources) if(DRACO_ENABLE_NEON AND draco_have_neon) unset(neon_sources) list(APPEND neon_sources ${arg_SOURCES}) - list(FILTER neon_sources INCLUDE REGEX - "${draco_neon_source_file_suffix}$") + list(FILTER neon_sources INCLUDE REGEX "${draco_neon_source_file_suffix}$") if(neon_sources AND DRACO_NEON_INTRINSICS_FLAG) unset(neon_flags) - draco_get_intrinsics_flag_for_suffix(SUFFIX - ${draco_neon_source_file_suffix} - VARIABLE neon_flags) + draco_get_intrinsics_flag_for_suffix( + SUFFIX ${draco_neon_source_file_suffix} VARIABLE neon_flags) if(neon_flags) draco_set_compiler_flags_for_sources(SOURCES ${neon_sources} FLAGS - ${neon_flags}) + ${neon_flags}) endif() endif() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_options.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_options.cmake index 832bfb69f..085149774 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_options.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_options.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_OPTIONS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_OPTIONS_CMAKE_ @@ -18,17 +32,22 @@ macro(draco_option) cmake_parse_arguments(option "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) - if(NOT (option_NAME AND option_HELPSTRING AND DEFINED option_VALUE)) + if(NOT + (option_NAME + AND option_HELPSTRING + AND DEFINED option_VALUE)) message(FATAL_ERROR "draco_option: NAME HELPSTRING and VALUE required.") endif() option(${option_NAME} ${option_HELPSTRING} ${option_VALUE}) if(DRACO_VERBOSE GREATER 2) - message("--------- draco_option ---------\n" "option_NAME=${option_NAME}\n" - "option_HELPSTRING=${option_HELPSTRING}\n" - "option_VALUE=${option_VALUE}\n" - "------------------------------------------\n") + message( + "--------- draco_option ---------\n" + "option_NAME=${option_NAME}\n" + "option_HELPSTRING=${option_HELPSTRING}\n" + "option_VALUE=${option_VALUE}\n" + "------------------------------------------\n") endif() list(APPEND draco_options ${option_NAME}) @@ -44,33 +63,74 @@ endmacro() # Set default options. macro(draco_set_default_options) - draco_option(NAME DRACO_FAST HELPSTRING "Try to build faster libs." VALUE OFF) - draco_option(NAME DRACO_JS_GLUE HELPSTRING - "Enable JS Glue and JS targets when using Emscripten." VALUE ON) - draco_option(NAME DRACO_IE_COMPATIBLE HELPSTRING - "Enable support for older IE builds when using Emscripten." VALUE - OFF) - draco_option(NAME DRACO_MESH_COMPRESSION HELPSTRING "Enable mesh compression." - VALUE ON) - draco_option(NAME DRACO_POINT_CLOUD_COMPRESSION HELPSTRING - "Enable point cloud compression." VALUE ON) - draco_option(NAME DRACO_PREDICTIVE_EDGEBREAKER HELPSTRING - "Enable predictive edgebreaker." VALUE ON) - draco_option(NAME DRACO_STANDARD_EDGEBREAKER HELPSTRING - "Enable stand edgebreaker." VALUE ON) - draco_option(NAME DRACO_BACKWARDS_COMPATIBILITY HELPSTRING - "Enable backwards compatibility." VALUE ON) - draco_option(NAME DRACO_DECODER_ATTRIBUTE_DEDUPLICATION HELPSTRING - "Enable attribute deduping." VALUE OFF) - draco_option(NAME DRACO_TESTS HELPSTRING "Enables tests." VALUE OFF) - draco_option(NAME DRACO_WASM HELPSTRING "Enables WASM support." VALUE OFF) - draco_option(NAME DRACO_UNITY_PLUGIN HELPSTRING - "Build plugin library for Unity." VALUE OFF) - draco_option(NAME DRACO_ANIMATION_ENCODING HELPSTRING "Enable animation." - VALUE OFF) - draco_option(NAME DRACO_GLTF HELPSTRING "Support GLTF." VALUE OFF) - draco_option(NAME DRACO_MAYA_PLUGIN HELPSTRING - "Build plugin library for Maya." VALUE OFF) + draco_option( + NAME DRACO_FAST + HELPSTRING "Try to build faster libs." + VALUE OFF) + draco_option( + NAME DRACO_JS_GLUE + HELPSTRING "Enable JS Glue and JS targets when using Emscripten." + VALUE ON) + draco_option( + NAME DRACO_IE_COMPATIBLE + HELPSTRING "Enable support for older IE builds when using Emscripten." + VALUE OFF) + draco_option( + NAME DRACO_MESH_COMPRESSION + HELPSTRING "Enable mesh compression." + VALUE ON) + draco_option( + NAME DRACO_POINT_CLOUD_COMPRESSION + HELPSTRING "Enable point cloud compression." + VALUE ON) + draco_option( + NAME DRACO_PREDICTIVE_EDGEBREAKER + HELPSTRING "Enable predictive edgebreaker." + VALUE ON) + draco_option( + NAME DRACO_STANDARD_EDGEBREAKER + HELPSTRING "Enable stand edgebreaker." + VALUE ON) + draco_option( + NAME DRACO_BACKWARDS_COMPATIBILITY + HELPSTRING "Enable backwards compatibility." + VALUE ON) + draco_option( + NAME DRACO_DECODER_ATTRIBUTE_DEDUPLICATION + HELPSTRING "Enable attribute deduping." + VALUE OFF) + draco_option( + NAME DRACO_TESTS + HELPSTRING "Enables tests." + VALUE OFF) + draco_option( + NAME DRACO_WASM + HELPSTRING "Enables WASM support." + VALUE OFF) + draco_option( + NAME DRACO_UNITY_PLUGIN + HELPSTRING "Build plugin library for Unity." + VALUE OFF) + draco_option( + NAME DRACO_ANIMATION_ENCODING + HELPSTRING "Enable animation." + VALUE OFF) + draco_option( + NAME DRACO_GLTF_BITSTREAM + HELPSTRING "Draco GLTF extension bitstream specified features only." + VALUE OFF) + draco_option( + NAME DRACO_MAYA_PLUGIN + HELPSTRING "Build plugin library for Maya." + VALUE OFF) + draco_option( + NAME DRACO_TRANSCODER_SUPPORTED + HELPSTRING "Enable the Draco transcoder." + VALUE OFF) + draco_option( + NAME DRACO_DEBUG_COMPILER_WARNINGS + HELPSTRING "Turn on more warnings." + VALUE OFF) draco_check_deprecated_options() endmacro() @@ -117,14 +177,16 @@ macro(draco_check_deprecated_options) DRACO_MAYA_PLUGIN) draco_handle_deprecated_option(OLDNAME BUILD_USD_PLUGIN NEWNAME BUILD_SHARED_LIBS) + draco_handle_deprecated_option(OLDNAME DRACO_GLTF NEWNAME + DRACO_GLTF_BITSTREAM) endmacro() # Macro for setting Draco features based on user configuration. Features enabled # by this macro are Draco global. macro(draco_set_optional_features) - if(DRACO_GLTF) - # Override settings when building for GLTF. + if(DRACO_GLTF_BITSTREAM) + # Enable only the features included in the Draco GLTF bitstream spec. draco_enable_feature(FEATURE "DRACO_MESH_COMPRESSION_SUPPORTED") draco_enable_feature(FEATURE "DRACO_NORMAL_ENCODING_SUPPORTED") draco_enable_feature(FEATURE "DRACO_STANDARD_EDGEBREAKER_SUPPORTED") @@ -170,6 +232,11 @@ macro(draco_set_optional_features) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() + if(DRACO_TRANSCODER_SUPPORTED) + draco_enable_feature(FEATURE "DRACO_TRANSCODER_SUPPORTED") + endif() + + endmacro() # Macro that handles tracking of Draco preprocessor symbols for the purpose of @@ -221,8 +288,56 @@ function(draco_generate_features_h) file(APPEND "${draco_features_file_name}.new" "#define ${feature}\n") endforeach() + if(MSVC) + if(NOT DRACO_DEBUG_COMPILER_WARNINGS) + file(APPEND "${draco_features_file_name}.new" + "// Enable DRACO_DEBUG_COMPILER_WARNINGS at CMake generation \n" + "// time to remove these pragmas.\n") + + # warning C4018: '': signed/unsigned mismatch. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4018)\n") + + # warning C4146: unary minus operator applied to unsigned type, result + # still unsigned + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4146)\n") + + # warning C4244: 'return': conversion from '' to '', possible + # loss of data. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4244)\n") + + # warning C4267: 'initializing' conversion from '' to '', + # possible loss of data. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4267)\n") + + # warning C4305: 'context' : truncation from 'type1' to 'type2'. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4305)\n") + + # warning C4661: 'identifier' : no suitable definition provided for + # explicit template instantiation request. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4661)\n") + + # warning C4800: Implicit conversion from 'type' to bool. Possible + # information loss. + # Also, in older MSVC releases: + # warning C4800: 'type' : forcing value to bool 'true' or 'false' + # (performance warning). + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4800)\n") + + # warning C4804: '': unsafe use of type '' in operation. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4804)\n") + endif() + endif() + file(APPEND "${draco_features_file_name}.new" - "\n#endif // DRACO_FEATURES_H_") + "\n#endif // DRACO_FEATURES_H_\n") # Will replace ${draco_features_file_name} only if the file content has # changed. This prevents forced Draco rebuilds after CMake runs. diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_sanitizer.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_sanitizer.cmake index d2e41a6cb..77d141481 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_sanitizer.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_sanitizer.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ @@ -5,7 +19,9 @@ set(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ 1) # Handles the details of enabling sanitizers. macro(draco_configure_sanitizer) - if(DRACO_SANITIZE AND NOT EMSCRIPTEN AND NOT MSVC) + if(DRACO_SANITIZE + AND NOT EMSCRIPTEN + AND NOT MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(DRACO_SANITIZE MATCHES "cfi") list(APPEND SAN_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") @@ -13,8 +29,8 @@ macro(draco_configure_sanitizer) "-fuse-ld=gold") endif() - if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 - AND DRACO_SANITIZE MATCHES "integer|undefined") + if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 AND DRACO_SANITIZE MATCHES + "integer|undefined") list(APPEND SAN_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") endif() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_targets.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_targets.cmake index 0456c4d7b..c8c79f511 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_targets.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_targets.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_TARGETS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_TARGETS_CMAKE_ @@ -51,26 +65,33 @@ macro(draco_add_executable) unset(exe_LIB_DEPS) set(optional_args TEST) set(single_value_args NAME OUTPUT_NAME) - set(multi_value_args SOURCES DEFINES INCLUDES COMPILE_FLAGS LINK_FLAGS - OBJLIB_DEPS LIB_DEPS) + set(multi_value_args + SOURCES + DEFINES + INCLUDES + COMPILE_FLAGS + LINK_FLAGS + OBJLIB_DEPS + LIB_DEPS) cmake_parse_arguments(exe "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_executable ---------\n" - "exe_TEST=${exe_TEST}\n" - "exe_TEST_DEFINES_MAIN=${exe_TEST_DEFINES_MAIN}\n" - "exe_NAME=${exe_NAME}\n" - "exe_OUTPUT_NAME=${exe_OUTPUT_NAME}\n" - "exe_SOURCES=${exe_SOURCES}\n" - "exe_DEFINES=${exe_DEFINES}\n" - "exe_INCLUDES=${exe_INCLUDES}\n" - "exe_COMPILE_FLAGS=${exe_COMPILE_FLAGS}\n" - "exe_LINK_FLAGS=${exe_LINK_FLAGS}\n" - "exe_OBJLIB_DEPS=${exe_OBJLIB_DEPS}\n" - "exe_LIB_DEPS=${exe_LIB_DEPS}\n" - "------------------------------------------\n") + message( + "--------- draco_add_executable ---------\n" + "exe_TEST=${exe_TEST}\n" + "exe_TEST_DEFINES_MAIN=${exe_TEST_DEFINES_MAIN}\n" + "exe_NAME=${exe_NAME}\n" + "exe_OUTPUT_NAME=${exe_OUTPUT_NAME}\n" + "exe_SOURCES=${exe_SOURCES}\n" + "exe_DEFINES=${exe_DEFINES}\n" + "exe_INCLUDES=${exe_INCLUDES}\n" + "exe_COMPILE_FLAGS=${exe_COMPILE_FLAGS}\n" + "exe_LINK_FLAGS=${exe_LINK_FLAGS}\n" + "exe_OBJLIB_DEPS=${exe_OBJLIB_DEPS}\n" + "exe_LIB_DEPS=${exe_LIB_DEPS}\n" + "------------------------------------------\n") endif() if(NOT (exe_NAME AND exe_SOURCES)) @@ -87,7 +108,12 @@ macro(draco_add_executable) endif() add_executable(${exe_NAME} ${exe_SOURCES}) - set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) + + target_compile_features(${exe_NAME} PUBLIC cxx_std_11) + + if(NOT EMSCRIPTEN) + set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) + endif() if(exe_OUTPUT_NAME) set_target_properties(${exe_NAME} PROPERTIES OUTPUT_NAME ${exe_OUTPUT_NAME}) @@ -104,8 +130,8 @@ macro(draco_add_executable) endif() if(exe_COMPILE_FLAGS OR DRACO_CXX_FLAGS) - target_compile_options(${exe_NAME} - PRIVATE ${exe_COMPILE_FLAGS} ${DRACO_CXX_FLAGS}) + target_compile_options(${exe_NAME} PRIVATE ${exe_COMPILE_FLAGS} + ${DRACO_CXX_FLAGS}) endif() if(exe_LINK_FLAGS OR DRACO_EXE_LINKER_FLAGS) @@ -113,8 +139,8 @@ macro(draco_add_executable) list(APPEND exe_LINK_FLAGS "${DRACO_EXE_LINKER_FLAGS}") # LINK_FLAGS is managed as a string. draco_set_and_stringify(SOURCE "${exe_LINK_FLAGS}" DEST exe_LINK_FLAGS) - set_target_properties(${exe_NAME} - PROPERTIES LINK_FLAGS "${exe_LINK_FLAGS}") + set_target_properties(${exe_NAME} PROPERTIES LINK_FLAGS + "${exe_LINK_FLAGS}") else() target_link_options(${exe_NAME} PRIVATE ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) @@ -136,12 +162,7 @@ macro(draco_add_executable) endif() if(exe_LIB_DEPS) - unset(exe_static) - if("${CMAKE_EXE_LINKER_FLAGS} ${DRACO_EXE_LINKER_FLAGS}" MATCHES "static") - set(exe_static ON) - endif() - - if(exe_static AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + if(CMAKE_CXX_COMPILER_ID MATCHES "^Clang|^GNU") # Third party dependencies can introduce dependencies on system and test # libraries. Since the target created here is an executable, and CMake # does not provide a method of controlling order of link dependencies, @@ -149,6 +170,10 @@ macro(draco_add_executable) # ensure that dependencies of third party targets can be resolved when # those dependencies happen to be resolved by dependencies of the current # target. + # TODO(tomfinegan): For portability use LINK_GROUP with RESCAN instead of + # directly (ab)using compiler/linker specific flags once CMake v3.24 is in + # wider use. See: + # https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:LINK_GROUP list(INSERT exe_LIB_DEPS 0 -Wl,--start-group) list(APPEND exe_LIB_DEPS -Wl,--end-group) endif() @@ -209,27 +234,36 @@ macro(draco_add_library) unset(lib_TARGET_PROPERTIES) set(optional_args TEST) set(single_value_args NAME OUTPUT_NAME TYPE) - set(multi_value_args SOURCES DEFINES INCLUDES COMPILE_FLAGS LINK_FLAGS - OBJLIB_DEPS LIB_DEPS PUBLIC_INCLUDES TARGET_PROPERTIES) + set(multi_value_args + SOURCES + DEFINES + INCLUDES + COMPILE_FLAGS + LINK_FLAGS + OBJLIB_DEPS + LIB_DEPS + PUBLIC_INCLUDES + TARGET_PROPERTIES) cmake_parse_arguments(lib "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_library ---------\n" - "lib_TEST=${lib_TEST}\n" - "lib_NAME=${lib_NAME}\n" - "lib_OUTPUT_NAME=${lib_OUTPUT_NAME}\n" - "lib_TYPE=${lib_TYPE}\n" - "lib_SOURCES=${lib_SOURCES}\n" - "lib_DEFINES=${lib_DEFINES}\n" - "lib_INCLUDES=${lib_INCLUDES}\n" - "lib_COMPILE_FLAGS=${lib_COMPILE_FLAGS}\n" - "lib_LINK_FLAGS=${lib_LINK_FLAGS}\n" - "lib_OBJLIB_DEPS=${lib_OBJLIB_DEPS}\n" - "lib_LIB_DEPS=${lib_LIB_DEPS}\n" - "lib_PUBLIC_INCLUDES=${lib_PUBLIC_INCLUDES}\n" - "---------------------------------------\n") + message( + "--------- draco_add_library ---------\n" + "lib_TEST=${lib_TEST}\n" + "lib_NAME=${lib_NAME}\n" + "lib_OUTPUT_NAME=${lib_OUTPUT_NAME}\n" + "lib_TYPE=${lib_TYPE}\n" + "lib_SOURCES=${lib_SOURCES}\n" + "lib_DEFINES=${lib_DEFINES}\n" + "lib_INCLUDES=${lib_INCLUDES}\n" + "lib_COMPILE_FLAGS=${lib_COMPILE_FLAGS}\n" + "lib_LINK_FLAGS=${lib_LINK_FLAGS}\n" + "lib_OBJLIB_DEPS=${lib_OBJLIB_DEPS}\n" + "lib_LIB_DEPS=${lib_LIB_DEPS}\n" + "lib_PUBLIC_INCLUDES=${lib_PUBLIC_INCLUDES}\n" + "---------------------------------------\n") endif() if(NOT (lib_NAME AND lib_TYPE)) @@ -256,14 +290,24 @@ macro(draco_add_library) endif() add_library(${lib_NAME} ${lib_TYPE} ${lib_SOURCES}) + + target_compile_features(${lib_NAME} PUBLIC cxx_std_11) + + target_include_directories(${lib_NAME} PUBLIC $) + + if(BUILD_SHARED_LIBS) + # Enable PIC for all targets in shared configurations. + set_target_properties(${lib_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) + endif() + if(lib_SOURCES) draco_process_intrinsics_sources(TARGET ${lib_NAME} SOURCES ${lib_SOURCES}) endif() if(lib_OUTPUT_NAME) if(NOT (BUILD_SHARED_LIBS AND MSVC)) - set_target_properties(${lib_NAME} - PROPERTIES OUTPUT_NAME ${lib_OUTPUT_NAME}) + set_target_properties(${lib_NAME} PROPERTIES OUTPUT_NAME + ${lib_OUTPUT_NAME}) endif() endif() @@ -280,8 +324,8 @@ macro(draco_add_library) endif() if(lib_COMPILE_FLAGS OR DRACO_CXX_FLAGS) - target_compile_options(${lib_NAME} - PRIVATE ${lib_COMPILE_FLAGS} ${DRACO_CXX_FLAGS}) + target_compile_options(${lib_NAME} PRIVATE ${lib_COMPILE_FLAGS} + ${DRACO_CXX_FLAGS}) endif() if(lib_LINK_FLAGS) @@ -320,11 +364,12 @@ macro(draco_add_library) set_target_properties(${lib_NAME} PROPERTIES PREFIX "") endif() - # VERSION and SOVERSION as necessary - if(NOT lib_TYPE STREQUAL STATIC AND NOT lib_TYPE STREQUAL MODULE) - set_target_properties(${lib_NAME} PROPERTIES VERSION ${DRACO_VERSION}) - if(NOT MSVC) - set_target_properties(${lib_NAME} PROPERTIES SOVERSION ${DRACO_SOVERSION}) + if(NOT EMSCRIPTEN) + # VERSION and SOVERSION as necessary + if((lib_TYPE STREQUAL BUNDLE OR lib_TYPE STREQUAL SHARED) AND NOT MSVC) + set_target_properties( + ${lib_NAME} PROPERTIES VERSION ${DRACO_SOVERSION} + SOVERSION ${DRACO_SOVERSION_MAJOR}) endif() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_test_config.h.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_test_config.h.cmake index 77a574123..9bb174569 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_test_config.h.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_test_config.h.cmake @@ -1,3 +1,17 @@ +// Copyright 2021 The Draco Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef DRACO_TESTING_DRACO_TEST_CONFIG_H_ #define DRACO_TESTING_DRACO_TEST_CONFIG_H_ @@ -9,5 +23,6 @@ #define DRACO_TEST_DATA_DIR "${DRACO_TEST_DATA_DIR}" #define DRACO_TEST_TEMP_DIR "${DRACO_TEST_TEMP_DIR}" +#define DRACO_TEST_ROOT_DIR "${DRACO_TEST_ROOT_DIR}" #endif // DRACO_TESTING_DRACO_TEST_CONFIG_H_ diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_tests.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_tests.cmake index a6dfc5b57..1d905a969 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_tests.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_tests.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_TESTS_CMAKE) return() endif() @@ -10,6 +24,13 @@ set(draco_factory_test_sources "${draco_src_root}/io/file_reader_factory_test.cc" "${draco_src_root}/io/file_writer_factory_test.cc") +list( + APPEND draco_test_common_sources + "${draco_src_root}/core/draco_test_base.h" + "${draco_src_root}/core/draco_test_utils.cc" + "${draco_src_root}/core/draco_test_utils.h" + "${draco_src_root}/core/status.cc") + list( APPEND draco_test_sources @@ -30,22 +51,23 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoding_test.cc" "${draco_src_root}/core/buffer_bit_coding_test.cc" - "${draco_src_root}/core/draco_test_base.h" - "${draco_src_root}/core/draco_test_utils.cc" - "${draco_src_root}/core/draco_test_utils.h" "${draco_src_root}/core/math_utils_test.cc" "${draco_src_root}/core/quantization_utils_test.cc" "${draco_src_root}/core/status_test.cc" "${draco_src_root}/core/vector_d_test.cc" "${draco_src_root}/io/file_reader_test_common.h" "${draco_src_root}/io/file_utils_test.cc" + "${draco_src_root}/io/file_writer_utils_test.cc" "${draco_src_root}/io/stdio_file_reader_test.cc" "${draco_src_root}/io/stdio_file_writer_test.cc" "${draco_src_root}/io/obj_decoder_test.cc" "${draco_src_root}/io/obj_encoder_test.cc" "${draco_src_root}/io/ply_decoder_test.cc" "${draco_src_root}/io/ply_reader_test.cc" + "${draco_src_root}/io/stl_decoder_test.cc" + "${draco_src_root}/io/stl_encoder_test.cc" "${draco_src_root}/io/point_cloud_io_test.cc" + "${draco_src_root}/mesh/corner_table_test.cc" "${draco_src_root}/mesh/mesh_are_equivalent_test.cc" "${draco_src_root}/mesh/mesh_cleanup_test.cc" "${draco_src_root}/mesh/triangle_soup_mesh_builder_test.cc" @@ -54,47 +76,71 @@ list( "${draco_src_root}/point_cloud/point_cloud_builder_test.cc" "${draco_src_root}/point_cloud/point_cloud_test.cc") -list(APPEND draco_gtest_all - "${draco_root}/../googletest/googletest/src/gtest-all.cc") -list(APPEND draco_gtest_main - "${draco_root}/../googletest/googletest/src/gtest_main.cc") +if(DRACO_TRANSCODER_SUPPORTED) + list( + APPEND draco_test_sources + "${draco_src_root}/animation/animation_test.cc" + "${draco_src_root}/io/gltf_decoder_test.cc" + "${draco_src_root}/io/gltf_encoder_test.cc" + "${draco_src_root}/io/gltf_utils_test.cc" + "${draco_src_root}/io/gltf_test_helper.cc" + "${draco_src_root}/io/gltf_test_helper.h" + "${draco_src_root}/io/scene_io_test.cc" + "${draco_src_root}/io/texture_io_test.cc" + "${draco_src_root}/material/material_library_test.cc" + "${draco_src_root}/material/material_test.cc" + "${draco_src_root}/metadata/property_table_test.cc" + "${draco_src_root}/metadata/structural_metadata_test.cc" + "${draco_src_root}/scene/instance_array_test.cc" + "${draco_src_root}/scene/light_test.cc" + "${draco_src_root}/scene/mesh_group_test.cc" + "${draco_src_root}/scene/scene_test.cc" + "${draco_src_root}/scene/scene_are_equivalent_test.cc" + "${draco_src_root}/scene/scene_utils_test.cc" + "${draco_src_root}/scene/trs_matrix_test.cc" + "${draco_src_root}/texture/texture_library_test.cc" + "${draco_src_root}/texture/texture_map_test.cc" + "${draco_src_root}/texture/texture_transform_test.cc") + +endif() macro(draco_setup_test_targets) if(DRACO_TESTS) + draco_setup_googletest() + if(NOT (EXISTS ${draco_gtest_all} AND EXISTS ${draco_gtest_main})) - message(FATAL "googletest must be a sibling directory of ${draco_root}.") + message(FATAL_ERROR "googletest missing, run git submodule update --init") endif() list(APPEND draco_test_defines GTEST_HAS_PTHREAD=0) - draco_add_library(TEST - NAME - draco_gtest - TYPE - STATIC - SOURCES - ${draco_gtest_all} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths}) + draco_add_library( + TEST + NAME draco_test_common + TYPE STATIC + SOURCES ${draco_test_common_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) - draco_add_library(TEST - NAME - draco_gtest_main - TYPE - STATIC - SOURCES - ${draco_gtest_main} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths}) + draco_add_library( + TEST + NAME draco_gtest + TYPE STATIC + SOURCES ${draco_gtest_all} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) + + draco_add_library( + TEST + NAME draco_gtest_main + TYPE STATIC + SOURCES ${draco_gtest_main} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) set(DRACO_TEST_DATA_DIR "${draco_root}/testdata") set(DRACO_TEST_TEMP_DIR "${draco_build}/draco_test_temp") + set(DRACO_TEST_ROOT_DIR "${draco_root}") file(MAKE_DIRECTORY "${DRACO_TEST_TEMP_DIR}") # Sets DRACO_TEST_DATA_DIR and DRACO_TEST_TEMP_DIR. @@ -102,32 +148,24 @@ macro(draco_setup_test_targets) "${draco_build}/testing/draco_test_config.h") # Create the test targets. - draco_add_executable(NAME - draco_tests - SOURCES - ${draco_test_sources} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths} - LIB_DEPS - draco_static - draco_gtest - draco_gtest_main) + draco_add_executable( + TEST + NAME draco_tests + SOURCES ${draco_test_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths} + LIB_DEPS ${draco_dependency} draco_gtest draco_gtest_main + draco_test_common) + + draco_add_executable( + TEST + NAME draco_factory_tests + SOURCES ${draco_factory_test_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths} + LIB_DEPS ${draco_dependency} draco_gtest draco_gtest_main + draco_test_common) + - draco_add_executable(NAME - draco_factory_tests - SOURCES - ${draco_factory_test_sources} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths} - LIB_DEPS - draco_static - draco_gtest - draco_gtest_main) endif() endmacro() diff --git a/Engine/lib/assimp/contrib/draco/cmake/draco_variables.cmake b/Engine/lib/assimp/contrib/draco/cmake/draco_variables.cmake index 8dbc77a53..6d1b6a99d 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/draco_variables.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/draco_variables.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_VARIABLES_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_VARIABLES_CMAKE_ @@ -14,8 +28,7 @@ macro(draco_variable_must_be_directory variable_name) if("${${variable_name}}" STREQUAL "") message( - FATAL_ERROR - "Empty variable ${variable_name} is required to build draco.") + FATAL_ERROR "Empty variable ${variable_name} is required to build draco.") endif() if(NOT IS_DIRECTORY "${${variable_name}}") @@ -44,11 +57,13 @@ macro(draco_dump_cmake_flag_variables) list(APPEND flag_variables "CMAKE_CXX_FLAGS_INIT" "CMAKE_CXX_FLAGS" "CMAKE_EXE_LINKER_FLAGS_INIT" "CMAKE_EXE_LINKER_FLAGS") if(CMAKE_BUILD_TYPE) - list(APPEND flag_variables "CMAKE_BUILD_TYPE" - "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}_INIT" - "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" - "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}_INIT" - "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}") + list( + APPEND flag_variables + "CMAKE_BUILD_TYPE" + "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}_INIT" + "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" + "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}_INIT" + "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}") endif() foreach(flag_variable ${flag_variables}) message("${flag_variable}:${${flag_variable}}") diff --git a/Engine/lib/assimp/contrib/draco/cmake/sanitizers.cmake b/Engine/lib/assimp/contrib/draco/cmake/sanitizers.cmake deleted file mode 100644 index e720bc045..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/sanitizers.cmake +++ /dev/null @@ -1,19 +0,0 @@ -if(DRACO_CMAKE_SANITIZERS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_SANITIZERS_CMAKE_ 1) - -if(MSVC OR NOT SANITIZE) - return() -endif() - -include("${draco_root}/cmake/compiler_flags.cmake") - -string(TOLOWER ${SANITIZE} SANITIZE) - -# Require the sanitizer requested. -require_linker_flag("-fsanitize=${SANITIZE}") -require_compiler_flag("-fsanitize=${SANITIZE}" YES) - -# Make callstacks accurate. -require_compiler_flag("-fno-omit-frame-pointer -fno-optimize-sibling-calls" YES) diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake index 87e0b4a45..a55da20fa 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_AARCH64_LINUX_GNU_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_AARCH64_LINUX_GNU_CMAKE_ diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/android-ndk-common.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/android-ndk-common.cmake index 5126d6e29..80396af48 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/android-ndk-common.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/android-ndk-common.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ANDROID_NDK_COMMON_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/android.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/android.cmake index b8f576d5e..ba50576b7 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/android.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/android.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ANDROID_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_ANDROID_CMAKE_ @@ -16,9 +30,9 @@ if(NOT ANDROID_ABI) set(ANDROID_ABI arm64-v8a) endif() -# Force arm mode for 32-bit targets (instead of the default thumb) to improve -# performance. -if(NOT ANDROID_ARM_MODE) +# Force arm mode for 32-bit arm targets (instead of the default thumb) to +# improve performance. +if(ANDROID_ABI MATCHES "^armeabi" AND NOT ANDROID_ARM_MODE) set(ANDROID_ARM_MODE arm) endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-ios-common.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-ios-common.cmake index 65326d1c2..fab54bb39 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-ios-common.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-ios-common.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM_IOS_COMMON_CMAKE_) return() endif() @@ -13,5 +27,3 @@ set(CMAKE_C_COMPILER clang) set(CMAKE_C_COMPILER_ARG1 "-arch ${CMAKE_SYSTEM_PROCESSOR}") set(CMAKE_CXX_COMPILER clang++) set(CMAKE_CXX_COMPILER_ARG1 "-arch ${CMAKE_SYSTEM_PROCESSOR}") - -# TODO(tomfinegan): Handle bit code embedding. diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake index 6e45969e9..f1f83d67c 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM_LINUX_GNUEABIHF_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_ARM_LINUX_GNUEABIHF_CMAKE_ diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake index 4b6d366f0..80d452f97 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-ios.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-ios.cmake index c4ec7e3fa..5365d70f1 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-ios.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARM64_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake index 046ff0139..a332760b2 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_LINUX_GCC_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake index 80ee98b18..bedcc0cad 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-ios.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-ios.cmake index 8ddd6997b..43e208b1f 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-ios.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARMV7_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake index 9c9472319..730a87f4b 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_LINUX_GCC_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7s-ios.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7s-ios.cmake index b433025ba..472756117 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7s-ios.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/armv7s-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7S_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARMV7S_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/i386-ios.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/i386-ios.cmake index e9a105591..38989d225 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/i386-ios.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/i386-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_i386_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_i386_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake index d43383640..6f63f2c31 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake index d6fabeacc..7a630f4d4 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_64_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-ios.cmake b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-ios.cmake index 4c50a72a2..6946ce410 100644 --- a/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-ios.cmake +++ b/Engine/lib/assimp/contrib/draco/cmake/toolchains/x86_64-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_64_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_X86_64_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/Engine/lib/assimp/contrib/draco/cmake/util.cmake b/Engine/lib/assimp/contrib/draco/cmake/util.cmake deleted file mode 100644 index 813146a62..000000000 --- a/Engine/lib/assimp/contrib/draco/cmake/util.cmake +++ /dev/null @@ -1,79 +0,0 @@ -if(DRACO_CMAKE_UTIL_CMAKE_) - return() -endif() -set(DRACO_CMAKE_UTIL_CMAKE_ 1) - -# Creates dummy source file in $draco_build_dir named $basename.$extension and -# returns the full path to the dummy source file via the $out_file_path -# parameter. -function(create_dummy_source_file basename extension out_file_path) - set(dummy_source_file "${draco_build_dir}/${basename}.${extension}") - file(WRITE "${dummy_source_file}.new" - "// Generated file. DO NOT EDIT!\n" - "// ${target_name} needs a ${extension} file to force link language, \n" - "// or to silence a harmless CMake warning: Ignore me.\n" - "void ${target_name}_dummy_function(void) {}\n") - - # Will replace ${dummy_source_file} only if the file content has changed. - # This prevents forced Draco rebuilds after CMake runs. - configure_file("${dummy_source_file}.new" "${dummy_source_file}") - file(REMOVE "${dummy_source_file}.new") - - set(${out_file_path} ${dummy_source_file} PARENT_SCOPE) -endfunction() - -# Convenience function for adding a dummy source file to $target_name using -# $extension as the file extension. Wraps create_dummy_source_file(). -function(add_dummy_source_file_to_target target_name extension) - create_dummy_source_file("${target_name}" "${extension}" "dummy_source_file") - target_sources(${target_name} PRIVATE ${dummy_source_file}) -endfunction() - -# Extracts the version number from $version_file and returns it to the user via -# $version_string_out_var. This is achieved by finding the first instance of the -# kDracoVersion variable and then removing everything but the string literal -# assigned to the variable. Quotes and semicolon are stripped from the returned -# string. -function(extract_version_string version_file version_string_out_var) - file(STRINGS "${version_file}" draco_version REGEX "kDracoVersion") - list(GET draco_version 0 draco_version) - string(REPLACE "static const char kDracoVersion[] = " "" draco_version - "${draco_version}") - string(REPLACE ";" "" draco_version "${draco_version}") - string(REPLACE "\"" "" draco_version "${draco_version}") - set("${version_string_out_var}" "${draco_version}" PARENT_SCOPE) -endfunction() - -# Sets CMake compiler launcher to $launcher_name when $launcher_name is found in -# $PATH. Warns user about ignoring build flag $launcher_flag when $launcher_name -# is not found in $PATH. -function(set_compiler_launcher launcher_flag launcher_name) - find_program(launcher_path "${launcher_name}") - if(launcher_path) - set(CMAKE_C_COMPILER_LAUNCHER "${launcher_path}" PARENT_SCOPE) - set(CMAKE_CXX_COMPILER_LAUNCHER "${launcher_path}" PARENT_SCOPE) - message("--- Using ${launcher_name} as compiler launcher.") - else() - message( - WARNING "--- Cannot find ${launcher_name}, ${launcher_flag} ignored.") - endif() -endfunction() - -# Terminates CMake execution when $var_name is unset in the environment. Sets -# CMake variable to the value of the environment variable when the variable is -# present in the environment. -macro(require_variable var_name) - if("$ENV{${var_name}}" STREQUAL "") - message(FATAL_ERROR "${var_name} must be set in environment.") - endif() - set_variable_if_unset(${var_name} "") -endmacro() - -# Sets $var_name to $default_value if not already set. -macro(set_variable_if_unset var_name default_value) - if(NOT "$ENV{${var_name}}" STREQUAL "") - set(${var_name} $ENV{${var_name}}) - elseif(NOT ${var_name}) - set(${var_name} ${default_value}) - endif() -endmacro() diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.cc b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.cc new file mode 100644 index 000000000..471cf2942 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.cc @@ -0,0 +1,47 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/animation.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void Animation::Copy(const Animation &src) { + name_ = src.name_; + channels_.clear(); + for (int i = 0; i < src.NumChannels(); ++i) { + std::unique_ptr new_channel(new AnimationChannel()); + new_channel->Copy(*src.GetChannel(i)); + channels_.push_back(std::move(new_channel)); + } + + samplers_.clear(); + for (int i = 0; i < src.NumSamplers(); ++i) { + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->Copy(*src.GetSampler(i)); + samplers_.push_back(std::move(new_sampler)); + } + + node_animation_data_.clear(); + for (int i = 0; i < src.NumNodeAnimationData(); ++i) { + std::unique_ptr new_data(new NodeAnimationData()); + new_data->Copy(*src.GetNodeAnimationData(i)); + node_animation_data_.push_back(std::move(new_data)); + } +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.h b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.h new file mode 100644 index 000000000..3713f9886 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation.h @@ -0,0 +1,149 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_ANIMATION_H_ +#define DRACO_ANIMATION_ANIMATION_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/animation/node_animation_data.h" +#include "draco/core/status.h" + +namespace draco { + +// Struct to hold information about an animation's sampler. +struct AnimationSampler { + enum class SamplerInterpolation { LINEAR, STEP, CUBICSPLINE }; + + static std::string InterpolationToString(SamplerInterpolation value) { + switch (value) { + case SamplerInterpolation::STEP: + return "STEP"; + case SamplerInterpolation::CUBICSPLINE: + return "CUBICSPLINE"; + default: + return "LINEAR"; + } + } + + AnimationSampler() + : input_index(-1), + interpolation_type(SamplerInterpolation::LINEAR), + output_index(-1) {} + + void Copy(const AnimationSampler &src) { + input_index = src.input_index; + interpolation_type = src.interpolation_type; + output_index = src.output_index; + } + + int input_index; + SamplerInterpolation interpolation_type; + int output_index; +}; + +// Struct to hold information about an animation's channel. +struct AnimationChannel { + enum class ChannelTransformation { TRANSLATION, ROTATION, SCALE, WEIGHTS }; + + static std::string TransformationToString(ChannelTransformation value) { + switch (value) { + case ChannelTransformation::ROTATION: + return "rotation"; + case ChannelTransformation::SCALE: + return "scale"; + case ChannelTransformation::WEIGHTS: + return "weights"; + default: + return "translation"; + } + } + + AnimationChannel() + : target_index(-1), + transformation_type(ChannelTransformation::TRANSLATION), + sampler_index(-1) {} + + void Copy(const AnimationChannel &src) { + target_index = src.target_index; + transformation_type = src.transformation_type; + sampler_index = src.sampler_index; + } + + int target_index; + ChannelTransformation transformation_type; + int sampler_index; +}; + +// This class is used to hold data and information of glTF animations. +class Animation { + public: + Animation() {} + + void Copy(const Animation &src); + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + + // Returns the number of channels in an animation. + int NumChannels() const { return channels_.size(); } + // Returns the number of samplers in an animation. + int NumSamplers() const { return samplers_.size(); } + // Returns the number of accessors in an animation. + int NumNodeAnimationData() const { return node_animation_data_.size(); } + + // Returns a channel in the animation. + AnimationChannel *GetChannel(int index) { return channels_[index].get(); } + const AnimationChannel *GetChannel(int index) const { + return channels_[index].get(); + } + // Returns a sampler in the animation. + AnimationSampler *GetSampler(int index) { return samplers_[index].get(); } + const AnimationSampler *GetSampler(int index) const { + return samplers_[index].get(); + } + // Returns an accessor in the animation. + NodeAnimationData *GetNodeAnimationData(int index) { + return node_animation_data_[index].get(); + } + const NodeAnimationData *GetNodeAnimationData(int index) const { + return node_animation_data_[index].get(); + } + + void AddNodeAnimationData( + std::unique_ptr node_animation_data) { + node_animation_data_.push_back(std::move(node_animation_data)); + } + void AddSampler(std::unique_ptr sampler) { + samplers_.push_back(std::move(sampler)); + } + void AddChannel(std::unique_ptr channel) { + channels_.push_back(std::move(channel)); + } + + private: + std::string name_; + std::vector> samplers_; + std::vector> channels_; + std::vector> node_animation_data_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_ANIMATION_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/animation_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation_test.cc new file mode 100644 index 000000000..473938bca --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/animation_test.cc @@ -0,0 +1,71 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/animation.h" + +#include "draco/core/draco_test_base.h" +#include "draco/draco_features.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(AnimationTest, TestCopy) { + // Test copying of animation data. + draco::Animation src_anim; + ASSERT_TRUE(src_anim.GetName().empty()); + src_anim.SetName("Walking"); + ASSERT_EQ(src_anim.GetName(), "Walking"); + + std::unique_ptr src_sampler_0( + new draco::AnimationSampler()); + src_sampler_0->interpolation_type = + draco::AnimationSampler::SamplerInterpolation::CUBICSPLINE; + std::unique_ptr src_sampler_1( + new draco::AnimationSampler()); + src_sampler_1->Copy(*src_sampler_0); + + ASSERT_EQ(src_sampler_0->interpolation_type, + src_sampler_1->interpolation_type); + + src_sampler_1->interpolation_type = + draco::AnimationSampler::SamplerInterpolation::STEP; + + src_anim.AddSampler(std::move(src_sampler_0)); + src_anim.AddSampler(std::move(src_sampler_1)); + ASSERT_EQ(src_anim.NumSamplers(), 2); + + std::unique_ptr src_channel( + new draco::AnimationChannel()); + src_channel->transformation_type = + draco::AnimationChannel::ChannelTransformation::WEIGHTS; + src_anim.AddChannel(std::move(src_channel)); + ASSERT_EQ(src_anim.NumChannels(), 1); + + draco::Animation dst_anim; + dst_anim.Copy(src_anim); + + ASSERT_EQ(dst_anim.GetName(), src_anim.GetName()); + ASSERT_EQ(dst_anim.NumSamplers(), 2); + ASSERT_EQ(dst_anim.NumChannels(), 1); + + ASSERT_EQ(dst_anim.GetSampler(0)->interpolation_type, + src_anim.GetSampler(0)->interpolation_type); + ASSERT_EQ(dst_anim.GetSampler(1)->interpolation_type, + src_anim.GetSampler(1)->interpolation_type); + ASSERT_EQ(dst_anim.GetChannel(0)->transformation_type, + src_anim.GetChannel(0)->transformation_type); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc index 4a6491f9d..fcd0eaa6f 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc @@ -26,8 +26,9 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { bool CreateAndAddTimestamps(int32_t num_frames) { timestamps_.resize(num_frames); - for (int i = 0; i < timestamps_.size(); ++i) + for (int i = 0; i < timestamps_.size(); ++i) { timestamps_[i] = static_cast(i); + } return keyframe_animation_.SetTimestamps(timestamps_); } @@ -35,8 +36,9 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { uint32_t num_components) { // Create and add animation data with. animation_data_.resize(num_frames * num_components); - for (int i = 0; i < animation_data_.size(); ++i) + for (int i = 0; i < animation_data_.size(); ++i) { animation_data_[i] = static_cast(i); + } return keyframe_animation_.AddKeyframes(draco::DT_FLOAT32, num_components, animation_data_); } @@ -49,7 +51,7 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { ASSERT_EQ(animation0.num_animations(), animation1.num_animations()); if (quantized) { - // TODO(hemmer) : Add test for stable quantization. + // TODO(b/199760123) : Add test for stable quantization. // Quantization will result in slightly different values. // Skip comparing values. return; @@ -109,9 +111,8 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { } } - ASSERT_TRUE( - encoder.EncodeKeyframeAnimation(keyframe_animation_, options, &buffer) - .ok()); + DRACO_ASSERT_OK( + encoder.EncodeKeyframeAnimation(keyframe_animation_, options, &buffer)); draco::DecoderBuffer dec_decoder; draco::KeyframeAnimationDecoder decoder; @@ -122,8 +123,8 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { std::unique_ptr decoded_animation( new KeyframeAnimation()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_animation.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_animation.get())); // Verify if animation before and after compression is identical. CompareAnimationData(keyframe_animation_, diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_test.cc index bc92b25ff..94566972b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/keyframe_animation_test.cc @@ -24,8 +24,9 @@ class KeyframeAnimationTest : public ::testing::Test { bool CreateAndAddTimestamps(int32_t num_frames) { timestamps_.resize(num_frames); - for (int i = 0; i < timestamps_.size(); ++i) + for (int i = 0; i < timestamps_.size(); ++i) { timestamps_[i] = static_cast(i); + } return keyframe_animation_.SetTimestamps(timestamps_); } @@ -33,8 +34,9 @@ class KeyframeAnimationTest : public ::testing::Test { uint32_t num_components) { // Create and add animation data with. animation_data_.resize(num_frames * num_components); - for (int i = 0; i < animation_data_.size(); ++i) + for (int i = 0; i < animation_data_.size(); ++i) { animation_data_[i] = static_cast(i); + } return keyframe_animation_.AddKeyframes(draco::DT_FLOAT32, num_components, animation_data_); } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/node_animation_data.h b/Engine/lib/assimp/contrib/draco/src/draco/animation/node_animation_data.h new file mode 100644 index 000000000..7799e3376 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/node_animation_data.h @@ -0,0 +1,150 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ +#define DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/core/hash_utils.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" + +namespace draco { + +// This class is used to store information and data for animations that only +// affect the nodes. +// TODO(fgalligan): Think about changing the name of this class now that Skin +// is using it. +class NodeAnimationData { + public: + enum class Type { SCALAR, VEC3, VEC4, MAT4 }; + + NodeAnimationData() : type_(Type::SCALAR), count_(0), normalized_(false) {} + + void Copy(const NodeAnimationData &src) { + type_ = src.type_; + count_ = src.count_; + normalized_ = src.normalized_; + data_ = src.data_; + } + + Type type() const { return type_; } + int count() const { return count_; } + bool normalized() const { return normalized_; } + + std::vector *GetMutableData() { return &data_; } + const std::vector *GetData() const { return &data_; } + + void SetType(Type type) { type_ = type; } + void SetCount(int count) { count_ = count; } + void SetNormalized(bool normalized) { normalized_ = normalized; } + + int ComponentSize() const { return sizeof(float); } + int NumComponents() const { + switch (type_) { + case Type::SCALAR: + return 1; + case Type::VEC3: + return 3; + case Type::MAT4: + return 16; + default: + return 4; + } + } + + std::string TypeAsString() const { + switch (type_) { + case Type::SCALAR: + return "SCALAR"; + case Type::VEC3: + return "VEC3"; + case Type::MAT4: + return "MAT4"; + default: + return "VEC4"; + } + } + + bool operator==(const NodeAnimationData &nad) const { + return type_ == nad.type_ && count_ == nad.count_ && + normalized_ == nad.normalized_ && data_ == nad.data_; + } + + private: + Type type_; + int count_; + bool normalized_; + std::vector data_; +}; + +// Wrapper class for hashing NodeAnimationData. When using different containers, +// this class is preferable instead of copying the data in NodeAnimationData +// every time. +class NodeAnimationDataHash { + public: + NodeAnimationDataHash() = delete; + NodeAnimationDataHash &operator=(const NodeAnimationDataHash &) = delete; + NodeAnimationDataHash(NodeAnimationDataHash &&) = delete; + NodeAnimationDataHash &operator=(NodeAnimationDataHash &&) = delete; + + explicit NodeAnimationDataHash(const NodeAnimationData *nad) + : node_animation_data_(nad) { + hash_ = NodeAnimationDataHash::HashNodeAnimationData(*node_animation_data_); + } + + NodeAnimationDataHash(const NodeAnimationDataHash &nadh) { + node_animation_data_ = nadh.node_animation_data_; + hash_ = nadh.hash_; + } + + bool operator==(const NodeAnimationDataHash &nadh) const { + return *node_animation_data_ == *nadh.node_animation_data_; + } + + struct Hash { + size_t operator()(const NodeAnimationDataHash &nadh) const { + return nadh.hash_; + } + }; + + const NodeAnimationData *GetNodeAnimationData() { + return node_animation_data_; + } + + private: + // Returns a hash of |nad|. + static size_t HashNodeAnimationData(const NodeAnimationData &nad) { + size_t hash = 79; // Magic number. + hash = HashCombine(static_cast(nad.type()), hash); + hash = HashCombine(nad.count(), hash); + hash = HashCombine(nad.normalized(), hash); + const uint64_t data_hash = + FingerprintString(reinterpret_cast(nad.GetData()->data()), + nad.GetData()->size() * sizeof(float)); + hash = HashCombine(data_hash, hash); + return hash; + } + + const NodeAnimationData *node_animation_data_; + size_t hash_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.cc b/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.cc new file mode 100644 index 000000000..f232978c2 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.cc @@ -0,0 +1,29 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/skin.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void Skin::Copy(const Skin &s) { + inverse_bind_matrices_.Copy(s.GetInverseBindMatrices()); + joints_ = s.GetJoints(); + joint_root_index_ = s.GetJointRoot(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.h b/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.h new file mode 100644 index 000000000..81ca997eb --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/animation/skin.h @@ -0,0 +1,64 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_SKIN_H_ +#define DRACO_ANIMATION_SKIN_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +#include "draco/animation/node_animation_data.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +// This class is used to store information on animation skins. +class Skin { + public: + Skin() : joint_root_index_(-1) {} + + void Copy(const Skin &s); + + NodeAnimationData &GetInverseBindMatrices() { return inverse_bind_matrices_; } + const NodeAnimationData &GetInverseBindMatrices() const { + return inverse_bind_matrices_; + } + + int AddJoint(SceneNodeIndex index) { + joints_.push_back(index); + return joints_.size() - 1; + } + int NumJoints() const { return joints_.size(); } + SceneNodeIndex GetJoint(int index) const { return joints_[index]; } + SceneNodeIndex &GetJoint(int index) { return joints_[index]; } + const std::vector &GetJoints() const { return joints_; } + + void SetJointRoot(SceneNodeIndex index) { joint_root_index_ = index; } + SceneNodeIndex GetJointRoot() const { return joint_root_index_; } + + private: + NodeAnimationData inverse_bind_matrices_; + + // List of node indices that make up the joint hierarchy. + std::vector joints_; + SceneNodeIndex joint_root_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_SKIN_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/attributes/attribute_transform.cc b/Engine/lib/assimp/contrib/draco/src/draco/attributes/attribute_transform.cc index 174e6b822..fb2ed1829 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/attributes/attribute_transform.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/attributes/attribute_transform.cc @@ -28,12 +28,13 @@ std::unique_ptr AttributeTransform::InitTransformedAttribute( const PointAttribute &src_attribute, int num_entries) { const int num_components = GetTransformedNumComponents(src_attribute); const DataType dt = GetTransformedDataType(src_attribute); - GeometryAttribute va; - va.Init(src_attribute.attribute_type(), nullptr, num_components, dt, false, + GeometryAttribute ga; + ga.Init(src_attribute.attribute_type(), nullptr, num_components, dt, false, num_components * DataTypeLength(dt), 0); - std::unique_ptr transformed_attribute(new PointAttribute(va)); + std::unique_ptr transformed_attribute(new PointAttribute(ga)); transformed_attribute->Reset(num_entries); transformed_attribute->SetIdentityMapping(); + transformed_attribute->set_unique_id(src_attribute.unique_id()); return transformed_attribute; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.cc b/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.cc index b62478426..141130f43 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.cc @@ -26,7 +26,7 @@ GeometryAttribute::GeometryAttribute() unique_id_(0) {} void GeometryAttribute::Init(GeometryAttribute::Type attribute_type, - DataBuffer *buffer, int8_t num_components, + DataBuffer *buffer, uint8_t num_components, DataType data_type, bool normalized, int64_t byte_stride, int64_t byte_offset) { buffer_ = buffer; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.h b/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.h index f4d099b1b..28f743fa0 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/attributes/geometry_attribute.h @@ -15,12 +15,18 @@ #ifndef DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_ #define DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_ +#include #include +#include #include #include "draco/attributes/geometry_indices.h" #include "draco/core/data_buffer.h" #include "draco/core/hash_utils.h" +#include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#endif namespace draco { @@ -51,6 +57,16 @@ class GeometryAttribute { // predefined use case. Such attributes are often used for a shader specific // data. GENERIC, +#ifdef DRACO_TRANSCODER_SUPPORTED + // TODO(ostava): Adding a new attribute would be bit-stream change for GLTF. + // Older decoders wouldn't know what to do with this attribute type. This + // should be open-sourced only when we are ready to increase our bit-stream + // version. + TANGENT, + MATERIAL, + JOINTS, + WEIGHTS, +#endif // Total number of different attribute types. // Always keep behind all named attributes. NAMED_ATTRIBUTES_COUNT, @@ -58,7 +74,7 @@ class GeometryAttribute { GeometryAttribute(); // Initializes and enables the attribute. - void Init(Type attribute_type, DataBuffer *buffer, int8_t num_components, + void Init(Type attribute_type, DataBuffer *buffer, uint8_t num_components, DataType data_type, bool normalized, int64_t byte_stride, int64_t byte_offset); bool IsValid() const { return buffer_ != nullptr; } @@ -129,6 +145,17 @@ class GeometryAttribute { buffer_->Write(byte_pos, value, byte_stride()); } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Sets a value of an attribute entry. The input |value| must have + // |input_num_components| entries and it will be automatically converted to + // the internal format used by the geometry attribute. If the conversion is + // not possible, an error status will be returned. + template + Status ConvertAndSetAttributeValue(AttributeValueIndex avi, + int input_num_components, + const InputT *value); +#endif + // DEPRECATED: Use // ConvertValue(AttributeValueIndex att_id, // int out_num_components, @@ -233,10 +260,11 @@ class GeometryAttribute { // Returns the number of components that are stored for each entry. // For position attribute this is usually three (x,y,z), // while texture coordinates have two components (u,v). - int8_t num_components() const { return num_components_; } + uint8_t num_components() const { return num_components_; } // Indicates whether the data type should be normalized before interpretation, // that is, it should be divided by the max value of the data type. bool normalized() const { return normalized_; } + void set_normalized(bool normalized) { normalized_ = normalized; } // The buffer storing the entire data of the attribute. const DataBuffer *buffer() const { return buffer_; } // Returns the number of bytes between two attribute entries, this is, at @@ -260,7 +288,7 @@ class GeometryAttribute { // T is the stored attribute data type. // OutT is the desired data type of the attribute. template - bool ConvertTypedValue(AttributeValueIndex att_id, int8_t out_num_components, + bool ConvertTypedValue(AttributeValueIndex att_id, uint8_t out_num_components, OutT *out_value) const { const uint8_t *src_address = GetAddress(att_id); @@ -270,29 +298,10 @@ class GeometryAttribute { return false; } const T in_value = *reinterpret_cast(src_address); - - // Make sure the in_value fits within the range of values that OutT - // is able to represent. Perform the check only for integral types. - if (std::is_integral::value && std::is_integral::value) { - static constexpr OutT kOutMin = - std::is_signed::value ? std::numeric_limits::lowest() : 0; - if (in_value < kOutMin || in_value > std::numeric_limits::max()) { - return false; - } + if (!ConvertComponentValue(in_value, normalized_, + out_value + i)) { + return false; } - - out_value[i] = static_cast(in_value); - // When converting integer to floating point, normalize the value if - // necessary. - if (std::is_integral::value && std::is_floating_point::value && - normalized_) { - out_value[i] /= static_cast(std::numeric_limits::max()); - } - // TODO(ostava): Add handling of normalized attributes when converting - // between different integer representations. If the attribute is - // normalized, integer values should be converted as if they represent 0-1 - // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1> - // should be converted to range <0, 2^8 - 1>. src_address += sizeof(T); } // Fill empty data for unused output components if needed. @@ -302,12 +311,128 @@ class GeometryAttribute { return true; } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Function that converts input |value| from type T to the internal attribute + // representation defined by OutT and |num_components_|. + template + Status ConvertAndSetAttributeTypedValue(AttributeValueIndex avi, + int8_t input_num_components, + const T *value) { + uint8_t *address = GetAddress(avi); + + // Convert all components available in both the original and output formats. + for (int i = 0; i < num_components_; ++i) { + if (!IsAddressValid(address)) { + return ErrorStatus("GeometryAttribute: Invalid address."); + } + OutT *const out_value = reinterpret_cast(address); + if (i < input_num_components) { + if (!ConvertComponentValue(*(value + i), normalized_, + out_value)) { + return ErrorStatus( + "GeometryAttribute: Failed to convert component value."); + } + } else { + *out_value = static_cast(0); + } + address += sizeof(OutT); + } + return OkStatus(); + } +#endif // DRACO_TRANSCODER_SUPPORTED + + // Converts |in_value| of type T into |out_value| of type OutT. If + // |normalized| is true, any conversion between floating point and integer + // values will be treating integers as normalized types (the entire integer + // range will be used to represent 0-1 floating point range). + template + static bool ConvertComponentValue(const T &in_value, bool normalized, + OutT *out_value) { + // Make sure the |in_value| can be represented as an integral type OutT. + if (std::is_integral::value) { + // Make sure the |in_value| fits within the range of values that OutT + // is able to represent. Perform the check only for integral types. + if (!std::is_same::value && std::is_integral::value) { + static constexpr OutT kOutMin = + std::is_signed::value ? std::numeric_limits::min() : 0; + if (in_value < kOutMin || in_value > std::numeric_limits::max()) { + return false; + } + } + + // Check conversion of floating point |in_value| to integral value OutT. + if (std::is_floating_point::value) { + // Make sure the floating point |in_value| is not NaN and not Inf as + // integral type OutT is unable to represent these values. + if (sizeof(in_value) > sizeof(double)) { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } else if (sizeof(in_value) > sizeof(float)) { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } else { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } + + // Make sure the floating point |in_value| fits within the range of + // values that integral type OutT is able to represent. + if (in_value < std::numeric_limits::min() || + in_value >= std::numeric_limits::max()) { + return false; + } + } + } + + if (std::is_integral::value && std::is_floating_point::value && + normalized) { + // When converting integer to floating point, normalize the value if + // necessary. + *out_value = static_cast(in_value); + *out_value /= static_cast(std::numeric_limits::max()); + } else if (std::is_floating_point::value && + std::is_integral::value && normalized) { + // Converting from floating point to a normalized integer. + if (in_value > 1 || in_value < 0) { + // Normalized float values need to be between 0 and 1. + return false; + } + // TODO(ostava): Consider allowing float to normalized integer conversion + // for 64-bit integer types. Currently it doesn't work because we don't + // have a floating point type that could store all 64 bit integers. + if (sizeof(OutT) > 4) { + return false; + } + // Expand the float to the range of the output integer and round it to the + // nearest representable value. Use doubles for the math to ensure the + // integer values are represented properly during the conversion process. + *out_value = static_cast(std::floor( + in_value * static_cast(std::numeric_limits::max()) + + 0.5)); + } else { + *out_value = static_cast(in_value); + } + + // TODO(ostava): Add handling of normalized attributes when converting + // between different integer representations. If the attribute is + // normalized, integer values should be converted as if they represent 0-1 + // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1> + // should be converted to range <0, 2^8 - 1>. + return true; + } + DataBuffer *buffer_; // The buffer descriptor is stored at the time the buffer is attached to this // attribute. The purpose is to detect if any changes happened to the buffer // since the time it was attached. DataBufferDescriptor buffer_descriptor_; - int8_t num_components_; + uint8_t num_components_; DataType data_type_; bool normalized_; int64_t byte_stride_; @@ -323,6 +448,54 @@ class GeometryAttribute { friend struct GeometryAttributeHasher; }; +#ifdef DRACO_TRANSCODER_SUPPORTED +template +Status GeometryAttribute::ConvertAndSetAttributeValue(AttributeValueIndex avi, + int input_num_components, + const InputT *value) { + switch (this->data_type()) { + case DT_INT8: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT8: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT16: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT16: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_FLOAT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_FLOAT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_BOOL: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + default: + break; + } + return ErrorStatus( + "GeometryAttribute::SetAndConvertAttributeValue: Unsupported " + "attribute type."); +} +#endif + // Hashing support // Function object for using Attribute as a hash key. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.cc b/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.cc index b28f860c1..e54ab5427 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.cc @@ -222,4 +222,47 @@ AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues( } #endif +#ifdef DRACO_TRANSCODER_SUPPORTED +void PointAttribute::RemoveUnusedValues() { + if (is_mapping_identity()) { + return; // For identity mapping, all values are always used. + } + // For explicit mapping we need to check if any point is mapped to a value. + // If not we can delete the value. + IndexTypeVector is_value_used(size(), false); + int num_used_values = 0; + for (PointIndex pi(0); pi < indices_map_.size(); ++pi) { + const AttributeValueIndex avi = indices_map_[pi]; + if (!is_value_used[avi]) { + is_value_used[avi] = true; + num_used_values++; + } + } + if (num_used_values == size()) { + return; // All values are used. + } + + // Remap the values and update the point to value mapping. + IndexTypeVector + old_to_new_value_map(size(), kInvalidAttributeValueIndex); + AttributeValueIndex new_avi(0); + for (AttributeValueIndex avi(0); avi < size(); ++avi) { + if (!is_value_used[avi]) { + continue; + } + if (avi != new_avi) { + SetAttributeValue(new_avi, GetAddress(avi)); + } + old_to_new_value_map[avi] = new_avi++; + } + + // Remap all points to the new attribute values. + for (PointIndex pi(0); pi < indices_map_.size(); ++pi) { + indices_map_[pi] = old_to_new_value_map[indices_map_[pi]]; + } + + num_unique_entries_ = num_used_values; +} +#endif + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.h b/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.h index ee3662031..d55c50c8a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/attributes/point_attribute.h @@ -133,6 +133,12 @@ class PointAttribute : public GeometryAttribute { return attribute_transform_data_.get(); } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Removes unused values from the attribute. Value is unused when no point + // is mapped to the value. Only applicable when the mapping is not identity. + void RemoveUnusedValues(); +#endif + private: #ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED template diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc index 797c62f30..480e3ff34 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc @@ -15,14 +15,16 @@ #include "draco/compression/attributes/attributes_encoder.h" #include "draco/core/varint_encoding.h" +#include "draco/draco_features.h" namespace draco { AttributesEncoder::AttributesEncoder() : point_cloud_encoder_(nullptr), point_cloud_(nullptr) {} -AttributesEncoder::AttributesEncoder(int att_id) : AttributesEncoder() { - AddAttributeId(att_id); +AttributesEncoder::AttributesEncoder(int point_attrib_id) + : AttributesEncoder() { + AddAttributeId(point_attrib_id); } bool AttributesEncoder::Init(PointCloudEncoder *encoder, const PointCloud *pc) { @@ -37,7 +39,15 @@ bool AttributesEncoder::EncodeAttributesEncoderData(EncoderBuffer *out_buffer) { for (uint32_t i = 0; i < num_attributes(); ++i) { const int32_t att_id = point_attribute_ids_[i]; const PointAttribute *const pa = point_cloud_->attribute(att_id); - out_buffer->Encode(static_cast(pa->attribute_type())); + GeometryAttribute::Type type = pa->attribute_type(); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Attribute types TANGENT, MATERIAL, JOINTS, and WEIGHTS are not supported + // in the official bitstream. They will be encoded as GENERIC. + if (type > GeometryAttribute::GENERIC) { + type = GeometryAttribute::GENERIC; + } +#endif + out_buffer->Encode(static_cast(type)); out_buffer->Encode(static_cast(pa->data_type())); out_buffer->Encode(static_cast(pa->num_components())); out_buffer->Encode(static_cast(pa->normalized())); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc index e4d53485d..51c41cf7a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc @@ -72,16 +72,19 @@ class PointAttributeVectorOutputIterator { Self &operator*() { return *this; } // Still needed in some cases. - // TODO(hemmer): remove. + // TODO(b/199760123): Remove. // hardcoded to 3 based on legacy usage. const Self &operator=(const VectorD &val) { DRACO_DCHECK_EQ(attributes_.size(), 1); // Expect only ONE attribute. AttributeTuple &att = attributes_[0]; PointAttribute *attribute = std::get<0>(att); + const AttributeValueIndex avi = attribute->mapped_index(point_id_); + if (avi >= static_cast(attribute->size())) { + return *this; + } const uint32_t &offset = std::get<1>(att); DRACO_DCHECK_EQ(offset, 0); // expected to be zero - attribute->SetAttributeValue(attribute->mapped_index(point_id_), - &val[0] + offset); + attribute->SetAttributeValue(avi, &val[0] + offset); return *this; } // Additional operator taking std::vector as argument. @@ -89,6 +92,10 @@ class PointAttributeVectorOutputIterator { for (auto index = 0; index < attributes_.size(); index++) { AttributeTuple &att = attributes_[index]; PointAttribute *attribute = std::get<0>(att); + const AttributeValueIndex avi = attribute->mapped_index(point_id_); + if (avi >= static_cast(attribute->size())) { + return *this; + } const uint32_t &offset = std::get<1>(att); const uint32_t &data_size = std::get<3>(att); const uint32_t &num_components = std::get<4>(att); @@ -103,10 +110,6 @@ class PointAttributeVectorOutputIterator { // redirect to copied data data_source = reinterpret_cast(data_); } - const AttributeValueIndex avi = attribute->mapped_index(point_id_); - if (avi >= static_cast(attribute->size())) { - return *this; - } attribute->SetAttributeValue(avi, data_source); } return *this; @@ -195,54 +198,55 @@ bool KdTreeAttributesDecoder::DecodePortableAttributes( data_size, num_components); total_dimensionality += num_components; } - PointAttributeVectorOutputIterator out_it(atts); + typedef PointAttributeVectorOutputIterator OutIt; + OutIt out_it(atts); switch (compression_level) { case 0: { - DynamicIntegerPointsKdTreeDecoder<0> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<0, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 1: { - DynamicIntegerPointsKdTreeDecoder<1> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<1, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 2: { - DynamicIntegerPointsKdTreeDecoder<2> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<2, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 3: { - DynamicIntegerPointsKdTreeDecoder<3> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<3, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 4: { - DynamicIntegerPointsKdTreeDecoder<4> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<4, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 5: { - DynamicIntegerPointsKdTreeDecoder<5> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<5, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 6: { - DynamicIntegerPointsKdTreeDecoder<6> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<6, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; @@ -253,6 +257,19 @@ bool KdTreeAttributesDecoder::DecodePortableAttributes( return true; } +template +bool KdTreeAttributesDecoder::DecodePoints(int total_dimensionality, + int num_expected_points, + DecoderBuffer *in_buffer, + OutIteratorT *out_iterator) { + DynamicIntegerPointsKdTreeDecoder decoder(total_dimensionality); + if (!decoder.DecodePoints(in_buffer, *out_iterator, num_expected_points) || + decoder.num_decoded_points() != num_expected_points) { + return false; + } + return true; +} + bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( DecoderBuffer *in_buffer) { if (in_buffer->bitstream_version() >= DRACO_BITSTREAM_VERSION(2, 3)) { @@ -336,6 +353,10 @@ bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( return false; } if (method == KdTreeAttributesEncodingMethod::kKdTreeQuantizationEncoding) { + // This method only supports one attribute with exactly three components. + if (atts.size() != 1 || std::get<4>(atts[0]) != 3) { + return false; + } uint8_t compression_level = 0; if (!in_buffer->Decode(&compression_level)) { return false; @@ -376,7 +397,7 @@ bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( GetDecoder()->point_cloud()->attribute(att_id); attr->Reset(num_points); attr->SetIdentityMapping(); - }; + } PointAttributeVectorOutputIterator out_it(atts); @@ -455,7 +476,11 @@ bool KdTreeAttributesDecoder::TransformAttributeBackToSignedType( att->GetValue(avi, &unsigned_val[0]); for (int c = 0; c < att->num_components(); ++c) { // Up-cast |unsigned_val| to int32_t to ensure we don't overflow it for - // smaller data types. + // smaller data types. But first check that the up-casting does not cause + // signed integer overflow. + if (unsigned_val[c] > std::numeric_limits::max()) { + return false; + } signed_val[c] = static_cast( static_cast(unsigned_val[c]) + min_signed_values_[num_processed_signed_components + c]); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h index 87338d6b0..4af367a1a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h @@ -31,6 +31,10 @@ class KdTreeAttributesDecoder : public AttributesDecoder { bool TransformAttributesToOriginalFormat() override; private: + template + bool DecodePoints(int total_dimensionality, int num_expected_points, + DecoderBuffer *in_buffer, OutIteratorT *out_iterator); + template bool TransformAttributeBackToSignedType(PointAttribute *att, int num_processed_signed_components); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h index 8a6f25b66..b717d0dbe 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h @@ -61,7 +61,7 @@ class OctahedronToolBox { return false; } quantization_bits_ = q; - max_quantized_value_ = (1 << quantization_bits_) - 1; + max_quantized_value_ = (1u << quantization_bits_) - 1; max_value_ = max_quantized_value_ - 1; dequantization_scale_ = 2.f / max_value_; center_value_ = max_value_ / 2; @@ -208,7 +208,9 @@ class OctahedronToolBox { DRACO_DCHECK_LE(t, center_value_); DRACO_DCHECK_GE(s, -center_value_); DRACO_DCHECK_GE(t, -center_value_); - return std::abs(s) + std::abs(t) <= center_value_; + const uint32_t st = + static_cast(std::abs(s)) + static_cast(std::abs(t)); + return st <= center_value_; } void InvertDiamond(int32_t *s, int32_t *t) const { @@ -230,19 +232,29 @@ class OctahedronToolBox { sign_t = (*t > 0) ? 1 : -1; } - const int32_t corner_point_s = sign_s * center_value_; - const int32_t corner_point_t = sign_t * center_value_; - *s = 2 * *s - corner_point_s; - *t = 2 * *t - corner_point_t; + // Perform the addition and subtraction using unsigned integers to avoid + // signed integer overflows for bad data. Note that the result will be + // unchanged for non-overflowing cases. + const uint32_t corner_point_s = sign_s * center_value_; + const uint32_t corner_point_t = sign_t * center_value_; + uint32_t us = *s; + uint32_t ut = *t; + us = us + us - corner_point_s; + ut = ut + ut - corner_point_t; if (sign_s * sign_t >= 0) { - int32_t temp = *s; - *s = -*t; - *t = -temp; + uint32_t temp = us; + us = -ut; + ut = -temp; } else { - std::swap(*s, *t); + std::swap(us, ut); } - *s = (*s + corner_point_s) / 2; - *t = (*t + corner_point_t) / 2; + us = us + corner_point_s; + ut = ut + corner_point_t; + + *s = us; + *t = ut; + *s /= 2; + *t /= 2; } void InvertDirection(int32_t *s, int32_t *t) const { @@ -318,7 +330,7 @@ class OctahedronToolBox { // Remaining coordinate can be computed by projecting the (y, z) values onto // the surface of the octahedron. - const float x = 1.f - abs(y) - abs(z); + const float x = 1.f - std::abs(y) - std::abs(z); // |x| is essentially a signed distance from the diagonal edges of the // diamond shown on the figure above. It is positive for all points in the diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/point_d_vector.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/point_d_vector.h index 3b115d500..6ceb454ae 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/point_d_vector.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/point_d_vector.h @@ -16,7 +16,9 @@ #ifndef DRACO_COMPRESSION_ATTRIBUTES_POINT_D_VECTOR_H_ #define DRACO_COMPRESSION_ATTRIBUTES_POINT_D_VECTOR_H_ +#include #include +#include #include #include @@ -99,11 +101,17 @@ class PointDVector { data_(n_items * dimensionality), data0_(data_.data()) {} // random access iterator - class PointDVectorIterator - : public std::iterator { + class PointDVectorIterator { friend class PointDVector; public: + // Iterator traits expected by std libraries. + using iterator_category = std::random_access_iterator_tag; + using value_type = size_t; + using difference_type = size_t; + using pointer = PointDVector *; + using reference = PointDVector &; + // std::iter_swap is called inside of std::partition and needs this // specialized support PseudoPointD operator*() const { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h index 36c124baa..17899d054 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h @@ -22,6 +22,7 @@ #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_decoder.h" #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_parallelogram_shared.h" #include "draco/compression/bit_coders/rans_bit_decoder.h" +#include "draco/core/math_utils.h" #include "draco/core/varint_decoding.h" #include "draco/draco_features.h" @@ -161,7 +162,8 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramDecoder< if (!is_crease) { ++num_used_parallelograms; for (int j = 0; j < num_components; ++j) { - multi_pred_vals[j] += pred_vals[i][j]; + multi_pred_vals[j] = + AddAsUnsigned(multi_pred_vals[j], pred_vals[i][j]); } } } @@ -210,6 +212,9 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramDecoder< if (!DecodeVarint(&num_flags, buffer)) { return false; } + if (num_flags > this->mesh_data().corner_table()->num_corners()) { + return false; + } if (num_flags > 0) { is_crease_edge_[i].resize(num_flags); RAnsBitDecoder decoder; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h index 77df8ee24..736598b15 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h @@ -392,7 +392,7 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder< RAnsBitEncoder encoder; encoder.StartEncoding(); // Encode the crease edge flags in the reverse vertex order that is needed - // be the decoder. Note that for the currently supported mode, each vertex + // by the decoder. Note that for the currently supported mode, each vertex // has exactly |num_used_parallelograms| edges that need to be encoded. for (int j = static_cast(is_crease_edge_[i].size()) - num_used_parallelograms; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h index fc82e0a8f..9825c7261 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h @@ -18,6 +18,7 @@ #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_decoder.h" #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_parallelogram_shared.h" +#include "draco/core/math_utils.h" #include "draco/draco_features.h" namespace draco { @@ -89,7 +90,8 @@ bool MeshPredictionSchemeMultiParallelogramDecoder(data[data_offset + 1])); } - void ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, + bool ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, int data_id); private: @@ -123,6 +123,10 @@ bool MeshPredictionSchemeTexCoordsDecoder:: ComputeOriginalValues(const CorrType *in_corr, DataTypeT *out_data, int /* size */, int num_components, const PointIndex *entry_to_point_id_map) { + if (num_components != 2) { + // Corrupt/malformed input. Two output components are req'd. + return false; + } num_components_ = num_components; entry_to_point_id_map_ = entry_to_point_id_map; predicted_value_ = @@ -133,7 +137,9 @@ bool MeshPredictionSchemeTexCoordsDecoder:: static_cast(this->mesh_data().data_to_corner_map()->size()); for (int p = 0; p < corner_map_size; ++p) { const CornerIndex corner_id = this->mesh_data().data_to_corner_map()->at(p); - ComputePredictedValue(corner_id, out_data, p); + if (!ComputePredictedValue(corner_id, out_data, p)) { + return false; + } const int dst_offset = p * num_components; this->transform().ComputeOriginalValue( @@ -159,6 +165,11 @@ bool MeshPredictionSchemeTexCoordsDecoder:: if (num_orientations == 0) { return false; } + if (num_orientations > this->mesh_data().corner_table()->num_corners()) { + // We can't have more orientations than the maximum number of decoded + // values. + return false; + } orientations_.resize(num_orientations); bool last_orientation = true; RAnsBitDecoder decoder; @@ -177,7 +188,7 @@ bool MeshPredictionSchemeTexCoordsDecoder:: } template -void MeshPredictionSchemeTexCoordsDecoder:: +bool MeshPredictionSchemeTexCoordsDecoder:: ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, int data_id) { // Compute the predicted UV coordinate from the positions on all corners @@ -206,9 +217,17 @@ void MeshPredictionSchemeTexCoordsDecoder:: const Vector2f p_uv = GetTexCoordForEntryId(prev_data_id, data); if (p_uv == n_uv) { // We cannot do a reliable prediction on degenerated UV triangles. - predicted_value_[0] = static_cast(p_uv[0]); - predicted_value_[1] = static_cast(p_uv[1]); - return; + // Technically floats > INT_MAX are undefined, but compilers will + // convert those values to INT_MIN. We are being explicit here for asan. + for (const int i : {0, 1}) { + if (std::isnan(p_uv[i]) || static_cast(p_uv[i]) > INT_MAX || + static_cast(p_uv[i]) < INT_MIN) { + predicted_value_[i] = INT_MIN; + } else { + predicted_value_[i] = static_cast(p_uv[i]); + } + } + return true; } // Get positions at all corners. @@ -282,32 +301,40 @@ void MeshPredictionSchemeTexCoordsDecoder:: const float pnvs = pn_uv[1] * s + n_uv[1]; const float pnvt = pn_uv[1] * t; Vector2f predicted_uv; + if (orientations_.empty()) { + return false; + } // When decoding the data, we already know which orientation to use. const bool orientation = orientations_.back(); orientations_.pop_back(); - if (orientation) + if (orientation) { predicted_uv = Vector2f(pnus - pnvt, pnvs + pnut); - else + } else { predicted_uv = Vector2f(pnus + pnvt, pnvs - pnut); - + } if (std::is_integral::value) { // Round the predicted value for integer types. - if (std::isnan(predicted_uv[0])) { + // Technically floats > INT_MAX are undefined, but compilers will + // convert those values to INT_MIN. We are being explicit here for asan. + const double u = floor(predicted_uv[0] + 0.5); + if (std::isnan(u) || u > INT_MAX || u < INT_MIN) { predicted_value_[0] = INT_MIN; } else { - predicted_value_[0] = static_cast(floor(predicted_uv[0] + 0.5)); + predicted_value_[0] = static_cast(u); } - if (std::isnan(predicted_uv[1])) { + const double v = floor(predicted_uv[1] + 0.5); + if (std::isnan(v) || v > INT_MAX || v < INT_MIN) { predicted_value_[1] = INT_MIN; } else { - predicted_value_[1] = static_cast(floor(predicted_uv[1] + 0.5)); + predicted_value_[1] = static_cast(v); } } else { predicted_value_[0] = static_cast(predicted_uv[0]); predicted_value_[1] = static_cast(predicted_uv[1]); } - return; + + return true; } // Else we don't have available textures on both corners. For such case we // can't use positions for predicting the uv value and we resort to delta @@ -330,12 +357,13 @@ void MeshPredictionSchemeTexCoordsDecoder:: for (int i = 0; i < num_components_; ++i) { predicted_value_[i] = 0; } - return; + return true; } } for (int i = 0; i < num_components_; ++i) { predicted_value_[i] = data[data_offset + i]; } + return true; } } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h index 741ec66dc..44fcc7a6a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h @@ -98,7 +98,10 @@ bool MeshPredictionSchemeTexCoordsPortableEncoder(this->mesh_data().data_to_corner_map()->size() - 1); p >= 0; --p) { const CornerIndex corner_id = this->mesh_data().data_to_corner_map()->at(p); - predictor_.template ComputePredictedValue(corner_id, in_data, p); + if (!predictor_.template ComputePredictedValue(corner_id, in_data, + p)) { + return false; + } const int dst_offset = p * num_components; this->transform().ComputeCorrection(in_data + dst_offset, diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h index f05e5ddd7..26262fb13 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h @@ -17,6 +17,9 @@ #include +#include +#include + #include "draco/attributes/point_attribute.h" #include "draco/core/math_utils.h" #include "draco/core/vector_d.h" @@ -105,10 +108,14 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< next_data_id = mesh_data_.vertex_to_data_map()->at(next_vert_id); prev_data_id = mesh_data_.vertex_to_data_map()->at(prev_vert_id); + typedef VectorD Vec2; + typedef VectorD Vec3; + typedef VectorD Vec2u; + if (prev_data_id < data_id && next_data_id < data_id) { // Both other corners have available UV coordinates for prediction. - const VectorD n_uv = GetTexCoordForEntryId(next_data_id, data); - const VectorD p_uv = GetTexCoordForEntryId(prev_data_id, data); + const Vec2 n_uv = GetTexCoordForEntryId(next_data_id, data); + const Vec2 p_uv = GetTexCoordForEntryId(prev_data_id, data); if (p_uv == n_uv) { // We cannot do a reliable prediction on degenerated UV triangles. predicted_value_[0] = p_uv[0]; @@ -117,9 +124,9 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< } // Get positions at all corners. - const VectorD tip_pos = GetPositionForEntryId(data_id); - const VectorD next_pos = GetPositionForEntryId(next_data_id); - const VectorD prev_pos = GetPositionForEntryId(prev_data_id); + const Vec3 tip_pos = GetPositionForEntryId(data_id); + const Vec3 next_pos = GetPositionForEntryId(next_data_id); + const Vec3 prev_pos = GetPositionForEntryId(prev_data_id); // We use the positions of the above triangle to predict the texture // coordinate on the tip corner C. // To convert the triangle into the UV coordinate system we first compute @@ -135,17 +142,17 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // Where next_pos is point (N), prev_pos is point (P) and tip_pos is the // position of predicted coordinate (C). // - const VectorD pn = prev_pos - next_pos; + const Vec3 pn = prev_pos - next_pos; const uint64_t pn_norm2_squared = pn.SquaredNorm(); if (pn_norm2_squared != 0) { // Compute the projection of C onto PN by computing dot product of CN with // PN and normalizing it by length of PN. This gives us a factor |s| where // |s = PN.Dot(CN) / PN.SquaredNorm2()|. This factor can be used to // compute X in UV space |X_UV| as |X_UV = N_UV + s * PN_UV|. - const VectorD cn = tip_pos - next_pos; + const Vec3 cn = tip_pos - next_pos; const int64_t cn_dot_pn = pn.Dot(cn); - const VectorD pn_uv = p_uv - n_uv; + const Vec2 pn_uv = p_uv - n_uv; // Because we perform all computations with integers, we don't explicitly // compute the normalized factor |s|, but rather we perform all operations // over UV vectors in a non-normalized coordinate system scaled with a @@ -153,19 +160,30 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // // x_uv = X_UV * PN.Norm2Squared() // - const VectorD x_uv = - n_uv * pn_norm2_squared + (cn_dot_pn * pn_uv); - + const int64_t n_uv_absmax_element = + std::max(std::abs(n_uv[0]), std::abs(n_uv[1])); + if (n_uv_absmax_element > + std::numeric_limits::max() / pn_norm2_squared) { + // Return false if the below multiplication would overflow. + return false; + } + const int64_t pn_uv_absmax_element = + std::max(std::abs(pn_uv[0]), std::abs(pn_uv[1])); + if (cn_dot_pn > + std::numeric_limits::max() / pn_uv_absmax_element) { + // Return false if squared length calculation would overflow. + return false; + } + const Vec2 x_uv = n_uv * pn_norm2_squared + (cn_dot_pn * pn_uv); const int64_t pn_absmax_element = std::max(std::max(std::abs(pn[0]), std::abs(pn[1])), std::abs(pn[2])); if (cn_dot_pn > std::numeric_limits::max() / pn_absmax_element) { - // return false if squared length calculation would overflow. + // Return false if squared length calculation would overflow. return false; } // Compute squared length of vector CX in position coordinate system: - const VectorD x_pos = - next_pos + (cn_dot_pn * pn) / pn_norm2_squared; + const Vec3 x_pos = next_pos + (cn_dot_pn * pn) / pn_norm2_squared; const uint64_t cx_norm2_squared = (tip_pos - x_pos).SquaredNorm(); // Compute vector CX_UV in the uv space by rotating vector PN_UV by 90 @@ -182,7 +200,7 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // // cx_uv = CX.Norm2() * PN.Norm2() * Rot(PN_UV) // - VectorD cx_uv(pn_uv[1], -pn_uv[0]); // Rotated PN_UV. + Vec2 cx_uv(pn_uv[1], -pn_uv[0]); // Rotated PN_UV. // Compute CX.Norm2() * PN.Norm2() const uint64_t norm_squared = IntSqrt(cx_norm2_squared * pn_norm2_squared); @@ -191,17 +209,15 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // Predicted uv coordinate is then computed by either adding or // subtracting CX_UV to/from X_UV. - VectorD predicted_uv; + Vec2 predicted_uv; if (is_encoder_t) { // When encoding, compute both possible vectors and determine which one // results in a better prediction. // Both vectors need to be transformed back from the scaled space to // the real UV coordinate space. - const VectorD predicted_uv_0((x_uv + cx_uv) / - pn_norm2_squared); - const VectorD predicted_uv_1((x_uv - cx_uv) / - pn_norm2_squared); - const VectorD c_uv = GetTexCoordForEntryId(data_id, data); + const Vec2 predicted_uv_0((x_uv + cx_uv) / pn_norm2_squared); + const Vec2 predicted_uv_1((x_uv - cx_uv) / pn_norm2_squared); + const Vec2 c_uv = GetTexCoordForEntryId(data_id, data); if ((c_uv - predicted_uv_0).SquaredNorm() < (c_uv - predicted_uv_1).SquaredNorm()) { predicted_uv = predicted_uv_0; @@ -217,10 +233,12 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< } const bool orientation = orientations_.back(); orientations_.pop_back(); + // Perform operations in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). if (orientation) { - predicted_uv = (x_uv + cx_uv) / pn_norm2_squared; + predicted_uv = Vec2(Vec2u(x_uv) + Vec2u(cx_uv)) / pn_norm2_squared; } else { - predicted_uv = (x_uv - cx_uv) / pn_norm2_squared; + predicted_uv = Vec2(Vec2u(x_uv) - Vec2u(cx_uv)) / pn_norm2_squared; } } predicted_value_[0] = static_cast(predicted_uv[0]); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc index f410a6cd2..2338f2f76 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc @@ -18,22 +18,58 @@ namespace draco { PredictionSchemeMethod SelectPredictionMethod( int att_id, const PointCloudEncoder *encoder) { - if (encoder->options()->GetSpeed() >= 10) { + return SelectPredictionMethod(att_id, *encoder->options(), encoder); +} + +PredictionSchemeMethod SelectPredictionMethod( + int att_id, const EncoderOptions &options, + const PointCloudEncoder *encoder) { + if (options.GetSpeed() >= 10) { // Selected fastest, though still doing some compression. return PREDICTION_DIFFERENCE; } if (encoder->GetGeometryType() == TRIANGULAR_MESH) { // Use speed setting to select the best encoding method. + const int att_quant = + options.GetAttributeInt(att_id, "quantization_bits", -1); const PointAttribute *const att = encoder->point_cloud()->attribute(att_id); - if (att->attribute_type() == GeometryAttribute::TEX_COORD) { - if (encoder->options()->GetSpeed() < 4) { + if (att_quant != -1 && + att->attribute_type() == GeometryAttribute::TEX_COORD && + att->num_components() == 2) { + // Texture coordinate predictor needs a position attribute that is either + // integer or quantized. For numerical reasons, we require the position + // quantization to be at most 21 bits and the 2*position_quantization + + // uv_quantization < 64 (TODO(b/231259902)). + const PointAttribute *const pos_att = + encoder->point_cloud()->GetNamedAttribute( + GeometryAttribute::POSITION); + bool is_pos_att_valid = false; + if (pos_att) { + if (IsDataTypeIntegral(pos_att->data_type())) { + is_pos_att_valid = true; + } else { + // Check quantization of the position attribute. + const int pos_att_id = encoder->point_cloud()->GetNamedAttributeId( + GeometryAttribute::POSITION); + const int pos_quant = + options.GetAttributeInt(pos_att_id, "quantization_bits", -1); + // Must be quantized but the quantization is restricted to 21 bits and + // 2*|pos_quant|+|att_quant| must be smaller than 64 bits. + if (pos_quant > 0 && pos_quant <= 21 && + 2 * pos_quant + att_quant < 64) { + is_pos_att_valid = true; + } + } + } + + if (is_pos_att_valid && options.GetSpeed() < 4) { // Use texture coordinate prediction for speeds 0, 1, 2, 3. return MESH_PREDICTION_TEX_COORDS_PORTABLE; } } if (att->attribute_type() == GeometryAttribute::NORMAL) { #ifdef DRACO_NORMAL_ENCODING_SUPPORTED - if (encoder->options()->GetSpeed() < 4) { + if (options.GetSpeed() < 4) { // Use geometric normal prediction for speeds 0, 1, 2, 3. // For this prediction, the position attribute needs to be either // integer or quantized as well. @@ -43,8 +79,8 @@ PredictionSchemeMethod SelectPredictionMethod( encoder->point_cloud()->GetNamedAttribute( GeometryAttribute::POSITION); if (pos_att && (IsDataTypeIntegral(pos_att->data_type()) || - encoder->options()->GetAttributeInt( - pos_att_id, "quantization_bits", -1) > 0)) { + options.GetAttributeInt(pos_att_id, "quantization_bits", + -1) > 0)) { return MESH_PREDICTION_GEOMETRIC_NORMAL; } } @@ -52,11 +88,10 @@ PredictionSchemeMethod SelectPredictionMethod( return PREDICTION_DIFFERENCE; // default } // Handle other attribute types. - if (encoder->options()->GetSpeed() >= 8) { + if (options.GetSpeed() >= 8) { return PREDICTION_DIFFERENCE; } - if (encoder->options()->GetSpeed() >= 2 || - encoder->point_cloud()->num_points() < 40) { + if (options.GetSpeed() >= 2 || encoder->point_cloud()->num_points() < 40) { // Parallelogram prediction is used for speeds 2 - 7 or when the overhead // of using constrained multi-parallelogram would be too high. return MESH_PREDICTION_PARALLELOGRAM; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h index 40a7683aa..11db5a62e 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h @@ -38,6 +38,10 @@ namespace draco { PredictionSchemeMethod SelectPredictionMethod(int att_id, const PointCloudEncoder *encoder); +PredictionSchemeMethod SelectPredictionMethod(int att_id, + const EncoderOptions &options, + const PointCloudEncoder *encoder); + // Factory class for creating mesh prediction schemes. template struct MeshPredictionSchemeEncoderFactory { @@ -97,10 +101,11 @@ CreatePredictionSchemeForEncoder(PredictionSchemeMethod method, int att_id, // template nature of the prediction schemes). const MeshEncoder *const mesh_encoder = static_cast(encoder); + const uint16_t bitstream_version = kDracoMeshBitstreamVersion; auto ret = CreateMeshPredictionScheme< MeshEncoder, PredictionSchemeEncoder, MeshPredictionSchemeEncoderFactory>( - mesh_encoder, method, att_id, transform, kDracoMeshBitstreamVersion); + mesh_encoder, method, att_id, transform, bitstream_version); if (ret) { return ret; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h index 5a6c7c2dd..e9e345343 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h @@ -21,6 +21,7 @@ #include "draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_base.h" #include "draco/core/decoder_buffer.h" #include "draco/core/macros.h" +#include "draco/core/math_utils.h" #include "draco/core/vector_d.h" namespace draco { @@ -98,9 +99,8 @@ class PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform if (!pred_is_in_bottom_left) { pred = this->RotatePoint(pred, rotation_count); } - Point2 orig = pred + corr; - orig[0] = this->ModMax(orig[0]); - orig[1] = this->ModMax(orig[1]); + Point2 orig(this->ModMax(AddAsUnsigned(pred[0], corr[0])), + this->ModMax(AddAsUnsigned(pred[1], corr[1]))); if (!pred_is_in_bottom_left) { const int32_t reverse_rotation_count = (4 - rotation_count) % 4; orig = this->RotatePoint(orig, reverse_rotation_count); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc index 8c8932f77..298758d8c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc @@ -25,10 +25,10 @@ class PredictionSchemeNormalOctahedronCanonicalizedTransformTest Transform; typedef Transform::Point2 Point2; - void TestComputeCorrection(const Transform &transform, const int32_t &ox, - const int32_t &oy, const int32_t &px, - const int32_t &py, const int32_t &cx, - const int32_t &cy) { + void TestComputeCorrection(const Transform &transform, const int32_t ox, + const int32_t oy, const int32_t px, + const int32_t py, const int32_t cx, + const int32_t cy) { const int32_t o[2] = {ox + 7, oy + 7}; const int32_t p[2] = {px + 7, py + 7}; int32_t corr[2] = {500, 500}; @@ -38,7 +38,7 @@ class PredictionSchemeNormalOctahedronCanonicalizedTransformTest } void TestGetRotationCount(const Transform &transform, const Point2 &pred, - const int32_t &rot_dir) { + const int32_t rot_dir) { const int32_t rotation_count = transform.GetRotationCount(pred); ASSERT_EQ(rot_dir, rotation_count); } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h index a1bc4a327..d3705c8ad 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h @@ -80,19 +80,31 @@ class PredictionSchemeNormalOctahedronDecodingTransform private: Point2 ComputeOriginalValue(Point2 pred, const Point2 &corr) const { const Point2 t(this->center_value(), this->center_value()); - pred = pred - t; + typedef typename std::make_unsigned::type UnsignedDataTypeT; + typedef VectorD Point2u; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + pred = Point2(Point2u(pred) - Point2u(t)); const bool pred_is_in_diamond = this->IsInDiamond(pred[0], pred[1]); if (!pred_is_in_diamond) { this->InvertDiamond(&pred[0], &pred[1]); } - Point2 orig = pred + corr; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + Point2 orig(Point2u(pred) + Point2u(corr)); + orig[0] = this->ModMax(orig[0]); orig[1] = this->ModMax(orig[1]); if (!pred_is_in_diamond) { this->InvertDiamond(&orig[0], &orig[1]); } - orig = orig + t; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + orig = Point2(Point2u(orig) + Point2u(t)); return orig; } }; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc index 1001b19fa..1403973c4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc @@ -23,10 +23,10 @@ class PredictionSchemeNormalOctahedronTransformTest : public ::testing::Test { Transform; typedef Transform::Point2 Point2; - void TestComputeCorrection(const Transform &transform, const int32_t &ox, - const int32_t &oy, const int32_t &px, - const int32_t &py, const int32_t &cx, - const int32_t &cy) { + void TestComputeCorrection(const Transform &transform, const int32_t ox, + const int32_t oy, const int32_t px, + const int32_t py, const int32_t cx, + const int32_t cy) { const int32_t o[2] = {ox + 7, oy + 7}; const int32_t p[2] = {px + 7, py + 7}; int32_t corr[2] = {500, 500}; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h index 26f61fbaf..bba3de09c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h @@ -70,10 +70,10 @@ class PredictionSchemeWrapTransformBase { clamped_value_[i] = predicted_val[i]; } } - return &clamped_value_[0]; + return clamped_value_.data(); } - // TODO(hemmer): Consider refactoring to avoid this dummy. + // TODO(b/199760123): Consider refactoring to avoid this dummy. int quantization_bits() const { DRACO_DCHECK(false); return -1; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc index 83f42125a..17f32fc16 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc @@ -148,8 +148,9 @@ bool SequentialIntegerAttributeDecoder::DecodeIntegerValues( return false; } for (size_t i = 0; i < num_values; ++i) { - if (!in_buffer->Decode(portable_attribute_data + i, num_bytes)) + if (!in_buffer->Decode(portable_attribute_data + i, num_bytes)) { return false; + } } } } @@ -228,12 +229,13 @@ void SequentialIntegerAttributeDecoder::StoreTypedValues(uint32_t num_values) { void SequentialIntegerAttributeDecoder::PreparePortableAttribute( int num_entries, int num_components) { - GeometryAttribute va; - va.Init(attribute()->attribute_type(), nullptr, num_components, DT_INT32, + GeometryAttribute ga; + ga.Init(attribute()->attribute_type(), nullptr, num_components, DT_INT32, false, num_components * DataTypeLength(DT_INT32), 0); - std::unique_ptr port_att(new PointAttribute(va)); + std::unique_ptr port_att(new PointAttribute(ga)); port_att->SetIdentityMapping(); port_att->Reset(num_entries); + port_att->set_unique_id(attribute()->unique_id()); SetPortableAttribute(std::move(port_att)); } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc index e66a0a8a4..5f673be42 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc @@ -138,9 +138,11 @@ bool SequentialIntegerAttributeEncoder::EncodeValues( // All integer values are initialized. Process them using the prediction // scheme if we have one. if (prediction_scheme_) { - prediction_scheme_->ComputeCorrectionValues( - portable_attribute_data, &encoded_data[0], num_values, num_components, - point_ids.data()); + if (!prediction_scheme_->ComputeCorrectionValues( + portable_attribute_data, &encoded_data[0], num_values, + num_components, point_ids.data())) { + return false; + } } if (prediction_scheme_ == nullptr || diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc index 2e20e89e6..3c5ef0ebc 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc @@ -20,8 +20,9 @@ namespace draco { bool SequentialNormalAttributeEncoder::Init(PointCloudEncoder *encoder, int attribute_id) { - if (!SequentialIntegerAttributeEncoder::Init(encoder, attribute_id)) + if (!SequentialIntegerAttributeEncoder::Init(encoder, attribute_id)) { return false; + } // Currently this encoder works only for 3-component normal vectors. if (attribute()->num_components() != 3) { return false; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h index b9fbc2d6f..6273692a2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h @@ -47,14 +47,13 @@ class DirectBitDecoder { // Decode the next |nbits| and return the sequence in |value|. |nbits| must be // > 0 and <= 32. - void DecodeLeastSignificantBits32(int nbits, uint32_t *value) { + bool DecodeLeastSignificantBits32(int nbits, uint32_t *value) { DRACO_DCHECK_EQ(true, nbits <= 32); DRACO_DCHECK_EQ(true, nbits > 0); const int remaining = 32 - num_used_bits_; if (nbits <= remaining) { if (pos_ == bits_.end()) { - *value = 0; - return; + return false; } *value = (*pos_ << num_used_bits_) >> (32 - nbits); num_used_bits_ += nbits; @@ -64,8 +63,7 @@ class DirectBitDecoder { } } else { if (pos_ + 1 == bits_.end()) { - *value = 0; - return; + return false; } const uint32_t value_l = ((*pos_) << num_used_bits_); num_used_bits_ = nbits - remaining; @@ -73,6 +71,7 @@ class DirectBitDecoder { const uint32_t value_r = (*pos_) >> (32 - num_used_bits_); *value = (value_l >> (32 - num_used_bits_ - remaining)) | value_r; } + return true; } void EndDecoding() {} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/config/encoder_options.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/config/encoder_options.h index ed1b02068..e8a55bbba 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/config/encoder_options.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/config/encoder_options.h @@ -65,6 +65,10 @@ class EncoderOptionsBase : public DracoOptions { this->SetGlobalInt("encoding_speed", encoding_speed); this->SetGlobalInt("decoding_speed", decoding_speed); } + bool IsSpeedSet() const { + return this->IsGlobalOptionSet("encoding_speed") || + this->IsGlobalOptionSet("decoding_speed"); + } // Sets a given feature as supported or unsupported by the target decoder. // Encoder will always use only supported features when encoding the input diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/decode_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/decode_test.cc index 198714690..8f3e7f4e9 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/decode_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/decode_test.cc @@ -17,9 +17,11 @@ #include #include +#include "draco/compression/encode.h" #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/io/file_utils.h" +#include "draco/io/obj_encoder.h" namespace { @@ -166,4 +168,78 @@ TEST_F(DecodeTest, TestSkipAttributeTransformWithNoQuantization) { ASSERT_EQ(pos_att->GetAttributeTransformData(), nullptr); } +TEST_F(DecodeTest, TestSkipAttributeTransformUniqueId) { + // Tests that decoders preserve unique id of attributes even when their + // attribute transforms are skipped. + const std::string file_name = "cube_att.obj"; + auto src_mesh = draco::ReadMeshFromTestFile(file_name); + ASSERT_NE(src_mesh, nullptr); + + constexpr int kPosUniqueId = 7; + constexpr int kNormUniqueId = 42; + // Set unique ids for some of the attributes. + src_mesh + ->attribute( + src_mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION)) + ->set_unique_id(kPosUniqueId); + src_mesh + ->attribute( + src_mesh->GetNamedAttributeId(draco::GeometryAttribute::NORMAL)) + ->set_unique_id(kNormUniqueId); + + draco::EncoderBuffer encoder_buffer; + draco::Encoder encoder; + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 10); + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 11); + encoder.EncodeMeshToBuffer(*src_mesh, &encoder_buffer); + + // Create a draco decoding buffer. + draco::DecoderBuffer buffer; + buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + + // First we decode the mesh without skipping the attribute transforms. + draco::Decoder decoder_no_skip; + std::unique_ptr mesh_no_skip = + decoder_no_skip.DecodeMeshFromBuffer(&buffer).value(); + ASSERT_NE(mesh_no_skip, nullptr); + + // Now we decode it again while skipping some attributes. + draco::Decoder decoder_skip; + // Make sure we skip dequantization for the position and normal attribute. + decoder_skip.SetSkipAttributeTransform(draco::GeometryAttribute::POSITION); + decoder_skip.SetSkipAttributeTransform(draco::GeometryAttribute::NORMAL); + + // Decode the input data into a geometry. + buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + std::unique_ptr mesh_skip = + decoder_skip.DecodeMeshFromBuffer(&buffer).value(); + ASSERT_NE(mesh_skip, nullptr); + + // Compare the unique ids. + const draco::PointAttribute *const pos_att_no_skip = + mesh_no_skip->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att_no_skip, nullptr); + ASSERT_EQ(pos_att_no_skip->data_type(), draco::DataType::DT_FLOAT32); + + const draco::PointAttribute *const pos_att_skip = + mesh_skip->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att_skip, nullptr); + ASSERT_EQ(pos_att_skip->data_type(), draco::DataType::DT_INT32); + + const draco::PointAttribute *const norm_att_no_skip = + mesh_no_skip->GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_NE(norm_att_no_skip, nullptr); + ASSERT_EQ(norm_att_no_skip->data_type(), draco::DataType::DT_FLOAT32); + + const draco::PointAttribute *const norm_att_skip = + mesh_skip->GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_NE(norm_att_skip, nullptr); + ASSERT_EQ(norm_att_skip->data_type(), draco::DataType::DT_INT32); + + ASSERT_EQ(pos_att_skip->unique_id(), pos_att_no_skip->unique_id()); + ASSERT_EQ(norm_att_skip->unique_id(), norm_att_no_skip->unique_id()); + std::cout << pos_att_skip->unique_id() << " " << norm_att_skip->unique_id() + << std::endl; +} + } // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.cc new file mode 100644 index 000000000..08171c678 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.cc @@ -0,0 +1,59 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/compression/draco_compression_options.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +SpatialQuantizationOptions::SpatialQuantizationOptions(int quantization_bits) { + SetQuantizationBits(quantization_bits); +} + +void SpatialQuantizationOptions::SetQuantizationBits(int quantization_bits) { + mode_ = LOCAL_QUANTIZATION_BITS; + quantization_bits_ = quantization_bits; +} + +bool SpatialQuantizationOptions::AreQuantizationBitsDefined() const { + return mode_ == LOCAL_QUANTIZATION_BITS; +} + +SpatialQuantizationOptions &SpatialQuantizationOptions::SetGrid(float spacing) { + mode_ = GLOBAL_GRID; + spacing_ = spacing; + return *this; +} + +bool SpatialQuantizationOptions::operator==( + const SpatialQuantizationOptions &other) const { + if (mode_ != other.mode_) { + return false; + } + if (mode_ == LOCAL_QUANTIZATION_BITS) { + if (quantization_bits_ != other.quantization_bits_) { + return false; + } + } else if (mode_ == GLOBAL_GRID) { + if (spacing_ != other.spacing_) { + return false; + } + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.h new file mode 100644 index 000000000..31a4418ed --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options.h @@ -0,0 +1,141 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ +#define DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" + +namespace draco { + +// Quantization options for positions. Currently there are two modes for +// quantizing positions: +// +// 1. Quantization bits: +// - User defined number of quantization bits that is evenly distributed +// to cover the compressed geometry. +// 2. Grid: +// - Positions are snapped to a global grid defined by grid spacing. +// - This method is primarily intended to be used when the location of +// quantized vertices needs to be consistent between multiple +// geometries. +class SpatialQuantizationOptions { + public: + explicit SpatialQuantizationOptions(int quantization_bits); + + // Sets quantization bits that are going to be used for the compressed + // geometry. If the geometry is a scene, the same number of quantization bits + // is going to be applied to each mesh of the scene. Quantized values are + // going to be distributed within the bounds of individual meshes. + void SetQuantizationBits(int quantization_bits); + + // If this returns true, quantization_bits() should be used to get the + // desired number of quantization bits for compression. Otherwise the grid + // mode is selected and spacing() should be used to get the desired grid + // spacing. + bool AreQuantizationBitsDefined() const; + const int quantization_bits() const { return quantization_bits_; } + + // Defines quantization grid used for the compressed geometry. All vertices + // are going to be snapped to the nearest grid vertex that corresponds to an + // integer quantized position. |spacing| defines the distance between two grid + // vertices. E.g. a grid with |spacing| = 10 would have grid vertices at + // locations {10 * i, 10 * j, 10 * k} where i, j, k are integer numbers. + SpatialQuantizationOptions &SetGrid(float spacing); + + const float spacing() const { return spacing_; } + + bool operator==(const SpatialQuantizationOptions &other) const; + + private: + enum Mode { LOCAL_QUANTIZATION_BITS, GLOBAL_GRID }; + Mode mode_ = LOCAL_QUANTIZATION_BITS; + int quantization_bits_; // Default quantization bits for positions. + float spacing_ = 0.f; +}; + +// TODO(fgalligan): Add support for unified_position_quantization. +// Struct to hold Draco compression options. +struct DracoCompressionOptions { + int compression_level = 7; // compression level [0-10], most=10, least=0. + SpatialQuantizationOptions quantization_position{11}; + int quantization_bits_normal = 8; + int quantization_bits_tex_coord = 10; + int quantization_bits_color = 8; + int quantization_bits_generic = 8; + int quantization_bits_tangent = 8; + int quantization_bits_weight = 8; + bool find_non_degenerate_texture_quantization = false; + + bool operator==(const DracoCompressionOptions &other) const { + return compression_level == other.compression_level && + quantization_position == other.quantization_position && + quantization_bits_normal == other.quantization_bits_normal && + quantization_bits_tex_coord == other.quantization_bits_tex_coord && + quantization_bits_color == other.quantization_bits_color && + quantization_bits_generic == other.quantization_bits_generic && + quantization_bits_tangent == other.quantization_bits_tangent && + quantization_bits_weight == other.quantization_bits_weight && + find_non_degenerate_texture_quantization == + other.find_non_degenerate_texture_quantization; + } + + bool operator!=(const DracoCompressionOptions &other) const { + return !(*this == other); + } + + Status Check() const { + DRACO_RETURN_IF_ERROR( + Validate("Compression level", compression_level, 0, 10)); + if (quantization_position.AreQuantizationBitsDefined()) { + DRACO_RETURN_IF_ERROR(Validate("Position quantization", + quantization_position.quantization_bits(), + 0, 30)); + } else { + if (quantization_position.spacing() <= 0.f) { + return ErrorStatus("Position quantization spacing is invalid."); + } + } + DRACO_RETURN_IF_ERROR( + Validate("Normals quantization", quantization_bits_normal, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Tex coord quantization", quantization_bits_tex_coord, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Color quantization", quantization_bits_color, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Generic quantization", quantization_bits_generic, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Tangent quantization", quantization_bits_tangent, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Weights quantization", quantization_bits_weight, 0, 30)); + return OkStatus(); + } + + static Status Validate(const std::string &name, int value, int min, int max) { + if (value < min || value > max) { + const std::string range = + "[" + std::to_string(min) + "-" + std::to_string(max) + "]."; + return Status(Status::DRACO_ERROR, name + " is out of range " + range); + } + return OkStatus(); + } +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options_test.cc new file mode 100644 index 000000000..415295211 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/draco_compression_options_test.cc @@ -0,0 +1,45 @@ +#include "draco/compression/draco_compression_options.h" + +#include "draco/core/draco_test_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace { + +TEST(DracoCompressionOptionsTest, TestPositionQuantizationBits) { + // Test verifies that we can define draco compression options using + // quantization bits. + draco::SpatialQuantizationOptions options(10); + + // Quantization bits should be used by default. + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + ASSERT_EQ(options.quantization_bits(), 10); + + // Change the quantization bits. + options.SetQuantizationBits(9); + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + ASSERT_EQ(options.quantization_bits(), 9); + + // If we select the grid, quantization bits should not be used. + options.SetGrid(0.5f); + ASSERT_FALSE(options.AreQuantizationBitsDefined()); +} + +TEST(DracoCompressionOptionsTest, TestPositionQuantizationGrid) { + // Test verifies that we can define draco compression options using + // quantization grid. + draco::SpatialQuantizationOptions options(10); + + // Quantization bits should be used by default. + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + + // Set the grid parameters. + options.SetGrid(0.25f); + ASSERT_FALSE(options.AreQuantizationBitsDefined()); + + ASSERT_EQ(options.spacing(), 0.25f); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode.h index bce8b34c2..00ccb9b2e 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode.h @@ -129,7 +129,6 @@ class Encoder // call of EncodePointCloudToBuffer or EncodeMeshToBuffer is going to fail. void SetEncodingMethod(int encoding_method); - protected: // Creates encoder options for the expert encoder used during the actual // encoding. EncoderOptions CreateExpertEncoderOptions(const PointCloud &pc) const; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_base.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_base.h index c501bc4fa..6211efc22 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_base.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_base.h @@ -98,7 +98,7 @@ class EncoderBase { "Invalid prediction scheme for attribute type."); } } - // TODO(hemmer): Try to enable more prediction schemes for normals. + // TODO(b/199760123): Try to enable more prediction schemes for normals. if (att_type == GeometryAttribute::NORMAL) { if (!(prediction_scheme == PREDICTION_DIFFERENCE || prediction_scheme == MESH_PREDICTION_GEOMETRIC_NORMAL)) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_test.cc index fde4f6f5b..00d834703 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/encode_test.cc @@ -26,6 +26,7 @@ #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" +#include "draco/io/file_utils.h" #include "draco/io/obj_decoder.h" #include "draco/mesh/triangle_soup_mesh_builder.h" #include "draco/point_cloud/point_cloud_builder.h" @@ -213,16 +214,14 @@ class EncodeTest : public ::testing::Test { draco::Decoder decoder; if (mesh) { - auto maybe_mesh = decoder.DecodeMeshFromBuffer(&decoder_buffer); - ASSERT_TRUE(maybe_mesh.ok()); - auto decoded_mesh = std::move(maybe_mesh).value(); + DRACO_ASSIGN_OR_ASSERT(auto decoded_mesh, + decoder.DecodeMeshFromBuffer(&decoder_buffer)); ASSERT_NE(decoded_mesh, nullptr); ASSERT_EQ(decoded_mesh->num_points(), encoder.num_encoded_points()); ASSERT_EQ(decoded_mesh->num_faces(), encoder.num_encoded_faces()); } else { - auto maybe_pc = decoder.DecodePointCloudFromBuffer(&decoder_buffer); - ASSERT_TRUE(maybe_pc.ok()); - auto decoded_pc = std::move(maybe_pc).value(); + DRACO_ASSIGN_OR_ASSERT( + auto decoded_pc, decoder.DecodePointCloudFromBuffer(&decoder_buffer)); ASSERT_EQ(decoded_pc->num_points(), encoder.num_encoded_points()); } } @@ -274,7 +273,7 @@ TEST_F(EncodeTest, TestLinesObj) { encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 16); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestQuantizedInfinity) { @@ -315,7 +314,7 @@ TEST_F(EncodeTest, TestUnquantizedInfinity) { encoder.SetEncodingMethod(draco::POINT_CLOUD_SEQUENTIAL_ENCODING); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestQuantizedAndUnquantizedAttributes) { @@ -330,7 +329,7 @@ TEST_F(EncodeTest, TestQuantizedAndUnquantizedAttributes) { encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 11); encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 0); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestKdTreeEncoding) { @@ -348,7 +347,7 @@ TEST_F(EncodeTest, TestKdTreeEncoding) { // Now set quantization for the position attribute which should make // the encoder happy. encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 16); - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestTrackingOfNumberOfEncodedEntries) { @@ -373,7 +372,7 @@ TEST_F(EncodeTest, TestTrackingOfNumberOfEncodedEntriesNotSet) { draco::EncoderBuffer buffer; draco::Encoder encoder; - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); ASSERT_EQ(encoder.num_encoded_points(), 0); ASSERT_EQ(encoder.num_encoded_faces(), 0); } @@ -404,4 +403,170 @@ TEST_F(EncodeTest, TestNoPosQuantizationNormalCoding) { ASSERT_NE(decoded_mesh, nullptr); } +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(EncodeTest, TestDracoCompressionOptions) { + // This test verifies that we can set the encoder's compression options via + // draco::Mesh's compression options. + const auto mesh = draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh, nullptr); + + // First set compression level and quantization manually. + draco::Encoder encoder_manual; + draco::EncoderBuffer buffer_manual; + encoder_manual.SetAttributeQuantization(draco::GeometryAttribute::POSITION, + 8); + encoder_manual.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 7); + encoder_manual.SetSpeedOptions(4, 4); + + DRACO_ASSERT_OK(encoder_manual.EncodeMeshToBuffer(*mesh, &buffer_manual)); + + // Now do the same with options provided via DracoCompressionOptions. + draco::DracoCompressionOptions compression_options; + compression_options.compression_level = 6; + compression_options.quantization_position.SetQuantizationBits(8); + compression_options.quantization_bits_normal = 7; + mesh->SetCompressionOptions(compression_options); + mesh->SetCompressionEnabled(true); + + draco::Encoder encoder_auto; + draco::EncoderBuffer buffer_auto; + DRACO_ASSERT_OK(encoder_auto.EncodeMeshToBuffer(*mesh, &buffer_auto)); + + // Ensure that both encoders produce the same result. + ASSERT_EQ(buffer_manual.size(), buffer_auto.size()); + + // Now change some of the mesh's compression settings and ensure the + // compression changes as well. + compression_options.compression_level = 7; + mesh->SetCompressionOptions(compression_options); + buffer_auto.Clear(); + DRACO_ASSERT_OK(encoder_auto.EncodeMeshToBuffer(*mesh, &buffer_auto)); + ASSERT_NE(buffer_manual.size(), buffer_auto.size()); + + // Check that |mesh| compression options do not override the encoder options. + mesh->GetCompressionOptions().compression_level = 10; + mesh->GetCompressionOptions().quantization_position.SetQuantizationBits(10); + mesh->GetCompressionOptions().quantization_bits_normal = 10; + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder_manual.EncodeMeshToBuffer(*mesh, &buffer)); + ASSERT_EQ(buffer.size(), buffer_manual.size()); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsManualOverride) { + // This test verifies that we can use encoder's option to override compression + // options provided in draco::Mesh's compression options. + const auto mesh = draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh, nullptr); + + // Set some compression options. + draco::DracoCompressionOptions compression_options; + compression_options.compression_level = 6; + compression_options.quantization_position.SetQuantizationBits(8); + compression_options.quantization_bits_normal = 7; + mesh->SetCompressionOptions(compression_options); + mesh->SetCompressionEnabled(true); + + draco::Encoder encoder; + draco::EncoderBuffer buffer_no_override; + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer_no_override)); + + // Now override some options and ensure the compression is different. + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 5); + draco::EncoderBuffer buffer_with_override; + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer_with_override)); + ASSERT_LT(buffer_with_override.size(), buffer_no_override.size()); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsGridQuantization) { + // Test verifies that we can set position quantization via grid spacing. + + // 1x1x1 cube. + const auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + mesh->SetCompressionEnabled(true); + + // Set grid quantization for positions. + draco::DracoCompressionOptions compression_options; + // This should result in 10x10x10 quantization. + compression_options.quantization_position.SetGrid(0.1); + mesh->SetCompressionOptions(compression_options); + + draco::ExpertEncoder encoder(*mesh); + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(&buffer)); + + // The grid options should be reflected in the |encoder|. Check that the + // computed values are correct. + const int pos_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION); + draco::Vector3f origin; + encoder.options().GetAttributeVector(pos_att_id, "quantization_origin", 3, + &origin[0]); + ASSERT_EQ(origin, draco::Vector3f(0.f, 0.f, 0.f)); + + // We need 4 quantization bits (for 10 values). + ASSERT_EQ( + encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1), + 4); + + // The quantization range should be ((1 << quantization_bits) - 1) * spacing. + ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id, + "quantization_range", 0.f), + 15.f * 0.1f, 1e-6f); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsGridQuantizationWithOffset) { + // Test verifies that we can set position quantization via grid spacing when + // the geometry is not perfectly aligned with the quantization grid. + + // 1x1x1 cube. + const auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Move all positions a bit. + auto *pos_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION)); + for (draco::AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + draco::Vector3f pos; + pos_att->GetValue(avi, &pos[0]); + pos = pos + draco::Vector3f(-0.55f, 0.65f, 10.75f); + pos_att->SetAttributeValue(avi, &pos[0]); + } + + mesh->SetCompressionEnabled(true); + + // Set grid quantization for positions. + draco::DracoCompressionOptions compression_options; + // This should result in 16x16x16 quantization if the grid was perfectly + // aligned but since it is not we should expect 17 or 18 values per component. + compression_options.quantization_position.SetGrid(0.0625f); + mesh->SetCompressionOptions(compression_options); + + draco::ExpertEncoder encoder(*mesh); + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(&buffer)); + + // The grid options should be reflected in the |encoder|. Check that the + // computed values are correct. + const int pos_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION); + draco::Vector3f origin; + encoder.options().GetAttributeVector(pos_att_id, "quantization_origin", 3, + &origin[0]); + // The origin is the first lower value on the quantization grid for each + // component of the mesh. + ASSERT_EQ(origin, draco::Vector3f(-0.5625f, 0.625f, 10.75f)); + + // We need 5 quantization bits (for 17-18 values). + ASSERT_EQ( + encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1), + 5); + + // The quantization range should be ((1 << quantization_bits) - 1) * spacing. + ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id, + "quantization_range", 0.f), + 31.f * 0.0625f, 1e-6f); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/ans.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/ans.h index c71d58975..313546fee 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/ans.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/ans.h @@ -391,7 +391,6 @@ class RAnsEncoder { ans_.buf[ans_.buf_offset++] = ans_.state % DRACO_ANS_IO_BASE; ans_.state /= DRACO_ANS_IO_BASE; } - // TODO(ostava): The division and multiplication should be optimized. ans_.state = (ans_.state / p) * rans_precision + ans_.state % p + sym->cum_prob; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h index 10cdc6781..3b408c079 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h @@ -75,6 +75,13 @@ bool RAnsSymbolDecoder::Create( return false; } } + // Check that decoded number of symbols is not unreasonably high. Remaining + // buffer size must be at least |num_symbols| / 64 bytes to contain the + // probability table. The |prob_data| below is one byte but it can be + // theoretically stored for each 64th symbol. + if (num_symbols_ / 64 > buffer->remaining_size()) { + return false; + } probability_table_.resize(num_symbols_); if (num_symbols_ == 0) { return true; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h index 4e07ec871..4b738b50a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h @@ -125,8 +125,8 @@ bool RAnsSymbolEncoder::Create( for (int i = 0; i < num_symbols; ++i) { sorted_probabilities[i] = i; } - std::sort(sorted_probabilities.begin(), sorted_probabilities.end(), - ProbabilityLess(&probability_table_)); + std::stable_sort(sorted_probabilities.begin(), sorted_probabilities.end(), + ProbabilityLess(&probability_table_)); if (total_rans_prob < rans_precision_) { // This happens rather infrequently, just add the extra needed precision // to the most frequent symbol. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc index 93d29971c..79e811818 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc @@ -72,7 +72,7 @@ bool DecodeTaggedSymbols(uint32_t num_values, int num_components, int value_id = 0; for (uint32_t i = 0; i < num_values; i += num_components) { // Decode the tag. - const int bit_length = tag_decoder.DecodeSymbol(); + const uint32_t bit_length = tag_decoder.DecodeSymbol(); // Decode the actual value. for (int j = 0; j < num_components; ++j) { uint32_t val; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.cc index f9aec15eb..a3e649193 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.cc @@ -14,6 +14,12 @@ // #include "draco/compression/expert_encode.h" +#include +#include +#include +#include +#include + #include "draco/compression/mesh/mesh_edgebreaker_encoder.h" #include "draco/compression/mesh/mesh_sequential_encoder.h" #ifdef DRACO_POINT_CLOUD_COMPRESSION_SUPPORTED @@ -21,6 +27,9 @@ #include "draco/compression/point_cloud/point_cloud_sequential_encoder.h" #endif +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/bit_utils.h" +#endif namespace draco { ExpertEncoder::ExpertEncoder(const PointCloud &point_cloud) @@ -101,6 +110,11 @@ Status ExpertEncoder::EncodePointCloudToBuffer(const PointCloud &pc, Status ExpertEncoder::EncodeMeshToBuffer(const Mesh &m, EncoderBuffer *out_buffer) { +#ifdef DRACO_TRANSCODER_SUPPORTED + // Apply DracoCompressionOptions associated with the mesh. + DRACO_RETURN_IF_ERROR(ApplyCompressionOptions(m)); +#endif // DRACO_TRANSCODER_SUPPORTED + std::unique_ptr encoder; // Select the encoding method only based on the provided options. int encoding_method = options().GetGlobalInt("encoding_method", -1); @@ -118,6 +132,7 @@ Status ExpertEncoder::EncodeMeshToBuffer(const Mesh &m, encoder = std::unique_ptr(new MeshSequentialEncoder()); } encoder->SetMesh(m); + DRACO_RETURN_IF_ERROR(encoder->Encode(options(), out_buffer)); set_num_encoded_points(encoder->num_encoded_points()); @@ -179,4 +194,107 @@ Status ExpertEncoder::SetAttributePredictionScheme( return status; } +#ifdef DRACO_TRANSCODER_SUPPORTED +Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) { + if (!mesh.IsCompressionEnabled()) { + return OkStatus(); + } + const auto &compression_options = mesh.GetCompressionOptions(); + + // Set any encoder options that haven't been explicitly set by users (don't + // override existing options). + if (!options().IsSpeedSet()) { + options().SetSpeed(10 - compression_options.compression_level, + 10 - compression_options.compression_level); + } + + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + if (options().IsAttributeOptionSet(ai, "quantization_bits")) { + continue; // Don't override options that have been set. + } + int quantization_bits = 0; + const auto type = mesh.attribute(ai)->attribute_type(); + switch (type) { + case GeometryAttribute::POSITION: + if (compression_options.quantization_position + .AreQuantizationBitsDefined()) { + quantization_bits = + compression_options.quantization_position.quantization_bits(); + } else { + DRACO_RETURN_IF_ERROR(ApplyGridQuantization(mesh, ai)); + } + break; + case GeometryAttribute::TEX_COORD: + quantization_bits = compression_options.quantization_bits_tex_coord; + break; + case GeometryAttribute::NORMAL: + quantization_bits = compression_options.quantization_bits_normal; + break; + case GeometryAttribute::COLOR: + quantization_bits = compression_options.quantization_bits_color; + break; + case GeometryAttribute::TANGENT: + quantization_bits = compression_options.quantization_bits_tangent; + break; + case GeometryAttribute::WEIGHTS: + quantization_bits = compression_options.quantization_bits_weight; + break; + case GeometryAttribute::GENERIC: + quantization_bits = compression_options.quantization_bits_generic; + break; + default: + break; + } + if (quantization_bits > 0) { + options().SetAttributeInt(ai, "quantization_bits", quantization_bits); + } + } + return OkStatus(); +} + +Status ExpertEncoder::ApplyGridQuantization(const Mesh &mesh, + int attribute_index) { + const auto compression_options = mesh.GetCompressionOptions(); + if (mesh.attribute(attribute_index)->num_components() != 3) { + return ErrorStatus( + "Invalid number of components: Grid quantization is currently " + "supported only for 3D positions."); + } + const float spacing = compression_options.quantization_position.spacing(); + // Compute quantization properties based on the grid spacing. + const auto &bbox = mesh.ComputeBoundingBox(); + // Snap min and max points of the |bbox| to the quantization grid vertices. + Vector3f min_pos; + int num_values = 0; // Number of values that we need to encode. + for (int c = 0; c < 3; ++c) { + // Min / max position on grid vertices in grid coordinates. + const float min_grid_pos = floor(bbox.GetMinPoint()[c] / spacing); + const float max_grid_pos = ceil(bbox.GetMaxPoint()[c] / spacing); + + // Min pos on grid vertex in mesh coordinates. + min_pos[c] = min_grid_pos * spacing; + + const float component_num_values = + static_cast(max_grid_pos) - static_cast(min_grid_pos) + 1; + if (component_num_values > num_values) { + num_values = component_num_values; + } + } + // Now compute the number of bits needed to encode |num_values|. + int bits = MostSignificantBit(num_values); + if ((1 << bits) < num_values) { + // If the |num_values| is larger than number of values representable by + // |bits|, we need to use one more bit. This will be almost always true + // unless |num_values| was equal to 1 << |bits|. + bits++; + } + // Compute the range in mesh coordinates that matches the quantization bits. + // Note there are n-1 intervals between the |n| quantization values. + const float range = ((1 << bits) - 1) * spacing; + SetAttributeExplicitQuantization(attribute_index, bits, 3, min_pos.data(), + range); + return OkStatus(); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.h index ea59393d3..5c1485e1e 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/expert_encode.h @@ -138,6 +138,12 @@ class ExpertEncoder : public EncoderBase { Status EncodeMeshToBuffer(const Mesh &m, EncoderBuffer *out_buffer); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Applies compression options stored in |mesh|. + Status ApplyCompressionOptions(const Mesh &mesh); + Status ApplyGridQuantization(const Mesh &mesh, int attribute_index); +#endif // DRACO_TRANSCODER_SUPPORTED + const PointCloud *point_cloud_; const Mesh *mesh_; }; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc index 0bbbea4af..21ad9959c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc @@ -454,7 +454,7 @@ bool MeshEdgebreakerDecoderImpl::DecodeConnectivity() { #endif // Decode connectivity of non-position attributes. - if (attribute_data_.size() > 0) { + if (!attribute_data_.empty()) { #ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED if (decoder_->bitstream_version() < DRACO_BITSTREAM_VERSION(2, 1)) { for (CornerIndex ci(0); ci < corner_table_->num_corners(); ci += 3) { @@ -484,7 +484,10 @@ bool MeshEdgebreakerDecoderImpl::DecodeConnectivity() { attribute_data_[i].connectivity_data.AddSeamEdge(CornerIndex(c)); } // Recompute vertices from the newly added seam edges. - attribute_data_[i].connectivity_data.RecomputeVertices(nullptr, nullptr); + if (!attribute_data_[i].connectivity_data.RecomputeVertices(nullptr, + nullptr)) { + return false; + } } pos_encoding_data_.Init(corner_table_->num_vertices()); @@ -574,6 +577,17 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( const CornerIndex corner_b = corner_table_->Next(corner_table_->LeftMostCorner(vertex_x)); + if (corner_a == corner_b) { + // All matched corners must be different. + return -1; + } + if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex || + corner_table_->Opposite(corner_b) != kInvalidCornerIndex) { + // One of the corners is already opposite to an existing face, which + // should not happen unless the input was tampered with. + return -1; + } + // New tip corner. const CornerIndex corner(3 * face.value()); // Update opposite corner mappings. @@ -616,6 +630,11 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( return -1; } const CornerIndex corner_a = active_corner_stack.back(); + if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex) { + // Active corner is already opposite to an existing face, which should + // not happen unless the input was tampered with. + return -1; + } // First corner on the new face is either corner "l" or "r". const CornerIndex corner(3 * face.value()); @@ -681,10 +700,14 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( } const CornerIndex corner_a = active_corner_stack.back(); + if (corner_a == corner_b) { + // All matched corners must be different. + return -1; + } if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex || corner_table_->Opposite(corner_b) != kInvalidCornerIndex) { // One of the corners is already opposite to an existing face, which - // should not happen unless the input was tempered with. + // should not happen unless the input was tampered with. return -1; } @@ -713,9 +736,15 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( // Also update the vertex id at corner "n" and all corners that are // connected to it in the CCW direction. + const CornerIndex first_corner = corner_n; while (corner_n != kInvalidCornerIndex) { corner_table_->MapCornerToVertex(corner_n, vertex_p); corner_n = corner_table_->SwingLeft(corner_n); + if (corner_n == first_corner) { + // We reached the start again which should not happen for split + // symbols. + return -1; + } } // Make sure the old vertex n is now mapped to an invalid corner (make it // isolated). @@ -800,7 +829,7 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( return -1; // Unexpected number of decoded vertices. } // Decode start faces and connect them to the faces from the active stack. - while (active_corner_stack.size() > 0) { + while (!active_corner_stack.empty()) { const CornerIndex corner = active_corner_stack.back(); active_corner_stack.pop_back(); const bool interior_face = @@ -842,6 +871,18 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( const CornerIndex corner_c = corner_table_->Next(corner_table_->LeftMostCorner(vert_x)); + if (corner == corner_b || corner == corner_c || corner_b == corner_c) { + // All matched corners must be different. + return -1; + } + if (corner_table_->Opposite(corner) != kInvalidCornerIndex || + corner_table_->Opposite(corner_b) != kInvalidCornerIndex || + corner_table_->Opposite(corner_c) != kInvalidCornerIndex) { + // One of the corners is already opposite to an existing face, which + // should not happen unless the input was tampered with. + return -1; + } + const VertexIndex vert_p = corner_table_->Vertex(corner_table_->Next(corner_c)); @@ -894,6 +935,11 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( VertexCornersIterator vcit(corner_table_.get(), src_vert); for (; !vcit.End(); ++vcit) { const CornerIndex cid = vcit.Corner(); + if (corner_table_->Vertex(cid) != src_vert) { + // Vertex mapped to |cid| was not |src_vert|. This indicates corrupted + // data and we should terminate the decoding. + return -1; + } corner_table_->MapCornerToVertex(cid, invalid_vert); } corner_table_->SetLeftMostCorner(invalid_vert, diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc index 5aff5d8cc..a7f381480 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc @@ -31,7 +31,6 @@ bool MeshEdgebreakerEncoder::InitializeEncoder() { impl_ = nullptr; // For tiny meshes it's usually better to use the basic edgebreaker as the // overhead of the predictive one may turn out to be too big. - // TODO(b/111065939): Check if this can be improved. const bool is_tiny_mesh = mesh()->num_faces() < 1000; int selected_edgebreaker_method = diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc index 0791dc670..4bf6aa920 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc @@ -408,7 +408,7 @@ Status MeshEdgebreakerEncoderImpl::EncodeConnectivity() { init_face_connectivity_corners.begin(), init_face_connectivity_corners.end()); // Encode connectivity for all non-position attributes. - if (attribute_data_.size() > 0) { + if (!attribute_data_.empty()) { // Use the same order of corner that will be used by the decoder. visited_faces_.assign(mesh_->num_faces(), false); for (CornerIndex ci : processed_connectivity_corners_) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h index fb3377163..979e1d373 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h @@ -177,7 +177,6 @@ class MeshEdgebreakerEncoderImpl : public MeshEdgebreakerEncoderImplInterface { uint32_t num_split_symbols_; // Struct holding data used for encoding each non-position attribute. - // TODO(ostava): This should be probably renamed to something better. struct AttributeData { AttributeData() : attribute_index(-1), is_connectivity_used(true) {} int attribute_index; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc index 831388245..523303b09 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc @@ -44,7 +44,7 @@ class MeshEdgebreakerEncodingTest : public ::testing::Test { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder_options.SetSpeed(10 - compression_level, 10 - compression_level); encoder.SetMesh(*mesh); - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -52,15 +52,14 @@ class MeshEdgebreakerEncodingTest : public ::testing::Test { std::unique_ptr decoded_mesh(new Mesh()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh.get())); // Cleanup the input mesh to make sure that input and output can be // compared (edgebreaker method discards degenerated triangles and isolated // vertices). const MeshCleanupOptions options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh, options)) << "Failed to clean the input mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh, options)); MeshAreEquivalent eq; ASSERT_TRUE(eq(*mesh, *decoded_mesh.get())) @@ -102,8 +101,8 @@ TEST_F(MeshEdgebreakerEncodingTest, TestEncoderReuse) { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder.SetMesh(*mesh); EncoderBuffer buffer_0, buffer_1; - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer_0).ok()); - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer_1).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer_0)); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer_1)); // Make sure both buffer are identical. ASSERT_EQ(buffer_0.size(), buffer_1.size()); @@ -123,7 +122,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestDecoderReuse) { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder.SetMesh(*mesh); EncoderBuffer buffer; - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -133,13 +132,13 @@ TEST_F(MeshEdgebreakerEncodingTest, TestDecoderReuse) { // Decode the mesh two times. std::unique_ptr decoded_mesh_0(new Mesh()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh_0.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh_0.get())); dec_buffer.Init(buffer.data(), buffer.size()); std::unique_ptr decoded_mesh_1(new Mesh()); - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh_1.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh_1.get())); // Make sure both of the meshes are identical. MeshAreEquivalent eq; @@ -169,7 +168,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestSingleConnectivityEncoding) { encoder.SetAttributeQuantization(GeometryAttribute::TEX_COORD, 8); encoder.SetAttributeQuantization(GeometryAttribute::NORMAL, 8); encoder.SetEncodingMethod(MESH_EDGEBREAKER_ENCODING); - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -216,7 +215,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestWrongAttributeOrder) { encoder.SetAttributeQuantization(GeometryAttribute::POSITION, 8); encoder.SetAttributeQuantization(GeometryAttribute::NORMAL, 8); encoder.SetEncodingMethod(MESH_EDGEBREAKER_ENCODING); - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h index cb3c29dd6..c650bc352 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h @@ -50,8 +50,6 @@ namespace draco { // \ / S \ / / E \ // *-------* *-------* // -// TODO(ostava): Get rid of the topology bit pattern. It's important only for -// encoding but the algorithms should use EdgebreakerSymbol instead. enum EdgebreakerTopologyBitPattern { TOPOLOGY_C = 0x0, // 0 TOPOLOGY_S = 0x1, // 1 0 0 diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h index c00373727..89553e909 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h @@ -129,7 +129,11 @@ class MeshEdgebreakerTraversalValenceDecoder if (context_counter < 0) { return TOPOLOGY_INVALID; } - const int symbol_id = context_symbols_[active_context_][context_counter]; + const uint32_t symbol_id = + context_symbols_[active_context_][context_counter]; + if (symbol_id > 4) { + return TOPOLOGY_INVALID; + } last_symbol_ = edge_breaker_symbol_to_topology_id[symbol_id]; } else { #ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc index 55f683696..2dfdb58ef 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc @@ -78,9 +78,10 @@ class MeshEncoderTest : public ::testing::TestWithParam { encoder.SetAttributeQuantization(i, 12); } EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodeToBuffer(&buffer).ok()) - << "Failed encoding test mesh " << file_name << " with method " - << GetParam().encoding_method; + const Status status = encoder.EncodeToBuffer(&buffer); + EXPECT_TRUE(status.ok()) << "Failed encoding test mesh " << file_name + << " with method " << GetParam().encoding_method; + DRACO_ASSERT_OK(status); // Check that the encoded mesh was really encoded with the selected method. DecoderBuffer decoder_buffer; decoder_buffer.Init(buffer.data(), buffer.size()); @@ -88,6 +89,7 @@ class MeshEncoderTest : public ::testing::TestWithParam { uint8_t encoded_method; ASSERT_TRUE(decoder_buffer.Decode(&encoded_method)); ASSERT_EQ(encoded_method, method); + if (!FLAGS_update_golden_files) { EXPECT_TRUE( CompareGoldenFile(golden_file_name, buffer.data(), buffer.size())) diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc index be349f543..595a487a4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc @@ -96,7 +96,7 @@ bool MeshSequentialDecoder::DecodeConnectivity() { } mesh()->AddFace(face); } - } else if (mesh()->num_points() < (1 << 21) && + } else if (num_points < (1 << 21) && bitstream_version() >= DRACO_BITSTREAM_VERSION(2, 2)) { // Decode indices as uint32_t. for (uint32_t i = 0; i < num_faces; ++i) { @@ -158,6 +158,10 @@ bool MeshSequentialDecoder::DecodeAndDecompressIndices(uint32_t num_faces) { index_diff = -index_diff; } const int32_t index_value = index_diff + last_index_value; + if (index_value < 0) { + // Negative indices are not allowed. + return false; + } face[j] = index_value; last_index_value = index_value; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc index 02ac7779e..fd8b11392 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc @@ -32,8 +32,6 @@ Status MeshSequentialEncoder::EncodeConnectivity() { EncodeVarint(static_cast(mesh()->num_points()), buffer()); // We encode all attributes in the original (possibly duplicated) format. - // TODO(ostava): This may not be optimal if we have only one attribute or if - // all attributes share the same index mapping. if (options()->GetGlobalBool("compress_connectivity", false)) { // 0 = Encode compressed indices. buffer()->Encode(static_cast(0)); @@ -44,8 +42,6 @@ Status MeshSequentialEncoder::EncodeConnectivity() { // 1 = Encode indices directly. buffer()->Encode(static_cast(1)); // Store vertex indices using a smallest data type that fits their range. - // TODO(ostava): This can be potentially improved by using a tighter - // fit that is not bound by a bit-length of any particular data type. if (mesh()->num_points() < 256) { // Serialize indices as uint8_t. for (FaceIndex i(0); i < num_faces; ++i) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h index 672609642..6e2b05877 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h @@ -33,7 +33,6 @@ namespace draco { // Class that encodes mesh data using a simple binary representation of mesh's // connectivity and geometry. -// TODO(ostava): Use a better name. class MeshSequentialEncoder : public MeshEncoder { public: MeshSequentialEncoder(); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h index e66dd14b2..dd9738ba2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h @@ -25,7 +25,7 @@ namespace draco { // values based on the traversal of the encoded mesh. The class should be used // as the TraversalObserverT member of a Traverser class such as the // DepthFirstTraverser (depth_first_traverser.h). -// TODO(hemmer): rename to AttributeIndicesCodingTraverserObserver +// TODO(b/199760123): Rename to AttributeIndicesCodingTraverserObserver. template class MeshAttributeIndicesEncodingObserver { public: diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h index ebe1d5f7a..e55c93a79 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h @@ -25,7 +25,7 @@ namespace draco { // Sequencer that generates point sequence in an order given by a deterministic // traversal on the mesh surface. Note that all attributes encoded with this // sequence must share the same connectivity. -// TODO(hemmer): Consider refactoring such that this is an observer. +// TODO(b/199760123): Consider refactoring such that this is an observer. template class MeshTraversalSequencer : public PointsSequencer { public: diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h index 87bc2b7ef..55bafe7c4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h @@ -18,8 +18,10 @@ #define DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_DYNAMIC_INTEGER_POINTS_KD_TREE_DECODER_H_ #include +#include #include #include +#include #include "draco/compression/bit_coders/adaptive_rans_bit_decoder.h" #include "draco/compression/bit_coders/direct_bit_decoder.h" @@ -92,17 +94,29 @@ class DynamicIntegerPointsKdTreeDecoder { base_stack_(32 * dimension + 1, VectorUint32(dimension, 0)), levels_stack_(32 * dimension + 1, VectorUint32(dimension, 0)) {} - // Decodes a integer point cloud from |buffer|. + // Decodes an integer point cloud from |buffer|. Optional |oit_max_points| can + // be used to tell the decoder the maximum number of points accepted by the + // iterator. template bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &oit); + template + bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &oit, + uint32_t oit_max_points); + #ifndef DRACO_OLD_GCC template bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &&oit); + template + bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &&oit, + uint32_t oit_max_points); #endif // DRACO_OLD_GCC const uint32_t dimension() const { return dimension_; } + // Returns the number of decoded points. Must be called after DecodePoints(). + uint32_t num_decoded_points() const { return num_decoded_points_; } + private: uint32_t GetAxis(uint32_t num_remaining_points, const VectorUint32 &levels, uint32_t last_axis); @@ -146,8 +160,15 @@ template template bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( DecoderBuffer *buffer, OutputIteratorT &&oit) { + return DecodePoints(buffer, oit, std::numeric_limits::max()); +} + +template +template +bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( + DecoderBuffer *buffer, OutputIteratorT &&oit, uint32_t oit_max_points) { OutputIteratorT local = std::forward(oit); - return DecodePoints(buffer, local); + return DecodePoints(buffer, local, oit_max_points); } #endif // DRACO_OLD_GCC @@ -155,6 +176,13 @@ template template bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( DecoderBuffer *buffer, OutputIteratorT &oit) { + return DecodePoints(buffer, oit, std::numeric_limits::max()); +} + +template +template +bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( + DecoderBuffer *buffer, OutputIteratorT &oit, uint32_t oit_max_points) { if (!buffer->Decode(&bit_length_)) { return false; } @@ -167,6 +195,9 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( if (num_points_ == 0) { return true; } + if (num_points_ > oit_max_points) { + return false; + } num_decoded_points_ = 0; if (!numbers_decoder_.StartDecoding(buffer)) { @@ -227,7 +258,7 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( std::stack status_stack; status_stack.push(init_status); - // TODO(hemmer): use preallocated vector instead of stack. + // TODO(b/199760123): Use preallocated vector instead of stack. while (!status_stack.empty()) { const DecodingStatus status = status_stack.top(); status_stack.pop(); @@ -263,7 +294,8 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( // Fast decoding of remaining bits if number of points is 1 or 2. if (num_remaining_points <= 2) { - // TODO(hemmer): axes_ not necessary, remove would change bitstream! + // TODO(b/199760123): |axes_| not necessary, remove would change + // bitstream! axes_[0] = axis; for (uint32_t i = 1; i < dimension_; i++) { axes_[i] = DRACO_INCREMENT_MOD(axes_[i - 1], dimension_); @@ -273,8 +305,10 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( p_[axes_[j]] = 0; const uint32_t num_remaining_bits = bit_length_ - levels[axes_[j]]; if (num_remaining_bits) { - remaining_bits_decoder_.DecodeLeastSignificantBits32( - num_remaining_bits, &p_[axes_[j]]); + if (!remaining_bits_decoder_.DecodeLeastSignificantBits32( + num_remaining_bits, &p_[axes_[j]])) { + return false; + } } p_[axes_[j]] = old_base[axes_[j]] | p_[axes_[j]]; } @@ -299,7 +333,12 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( uint32_t number = 0; DecodeNumber(incoming_bits, &number); - uint32_t first_half = num_remaining_points / 2 - number; + uint32_t first_half = num_remaining_points / 2; + if (first_half < number) { + // Invalid |number|. + return false; + } + first_half -= number; uint32_t second_half = num_remaining_points - first_half; if (first_half != second_half) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h index 14fa32d70..65b3d07a6 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h @@ -280,7 +280,7 @@ void DynamicIntegerPointsKdTreeEncoder::EncodeInternal( std::stack status_stack; status_stack.push(init_status); - // TODO(hemmer): use preallocated vector instead of stack. + // TODO(b/199760123): Use preallocated vector instead of stack. while (!status_stack.empty()) { Status status = status_stack.top(); status_stack.pop(); @@ -305,7 +305,8 @@ void DynamicIntegerPointsKdTreeEncoder::EncodeInternal( // Fast encoding of remaining bits if number of points is 1 or 2. // Doing this also for 2 gives a slight additional speed up. if (num_remaining_points <= 2) { - // TODO(hemmer): axes_ not necessary, remove would change bitstream! + // TODO(b/199760123): |axes_| not necessary, remove would change + // bitstream! axes_[0] = axis; for (uint32_t i = 1; i < dimension_; i++) { axes_[i] = DRACO_INCREMENT_MOD(axes_[i - 1], dimension_); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h index 26ba94f1f..44c1b3d3a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h @@ -44,7 +44,7 @@ namespace draco { // there are more leading zeros, which is then compressed better by the // arithmetic encoding. -// TODO(hemmer): Remove class because it duplicates quantization code. +// TODO(b/199760123): Remove class because it duplicates quantization code. class FloatPointsTreeEncoder { public: explicit FloatPointsTreeEncoder(PointCloudCompressionMethod method); @@ -91,7 +91,7 @@ bool FloatPointsTreeEncoder::EncodePointCloud(InputIteratorT points_begin, // Collect necessary data for encoding. num_points_ = std::distance(points_begin, points_end); - // TODO(hemmer): Extend quantization tools to make this more automatic. + // TODO(b/199760123): Extend quantization tools to make this more automatic. // Compute range of points for quantization std::vector qpoints; qpoints.reserve(num_points_); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h index 94e523cad..bc31af586 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// TODO(hemmer): Make this a wrapper using DynamicIntegerPointsKdTreeDecoder. +// TODO(b/199760123): Make this a wrapper using +// DynamicIntegerPointsKdTreeDecoder. // // See integer_points_kd_tree_encoder.h for documentation. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h index b8811092e..654f14a78 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// TODO(hemmer): Make this a wrapper using DynamicIntegerPointsKdTreeEncoder. +// TODO(b/199760123): Make this a wrapper using +// DynamicIntegerPointsKdTreeEncoder. #ifndef DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ #define DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h index 01943ad9e..8ea0741da 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h @@ -22,7 +22,7 @@ namespace draco { -// TODO(hemmer): Make this a stable bounding box. +// TODO(b/199760123): Make this a stable bounding box. struct QuantizationInfo { uint32_t quantization_bits; float range; @@ -41,7 +41,7 @@ OutputIterator QuantizePoints3(const PointIterator &begin, max_range = std::max(std::fabs((*it)[2]), max_range); } - const uint32_t max_quantized_value((1 << info->quantization_bits) - 1); + const uint32_t max_quantized_value((1u << info->quantization_bits) - 1); Quantizer quantize; quantize.Init(max_range, max_quantized_value); info->range = max_range; @@ -66,7 +66,7 @@ void DequantizePoints3(const QPointIterator &begin, const QPointIterator &end, const uint32_t quantization_bits = info.quantization_bits; const float range = info.range; - const uint32_t max_quantized_value((1 << quantization_bits) - 1); + const uint32_t max_quantized_value((1u << quantization_bits) - 1); Dequantizer dequantize; dequantize.Init(range, max_quantized_value); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc index 2249bb09e..7a7b597f2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc @@ -68,7 +68,7 @@ class PointCloudKdTreeEncodingTest : public ::testing::Test { ++compression_level) { options.SetSpeed(10 - compression_level, 10 - compression_level); encoder.SetPointCloud(pc); - ASSERT_TRUE(encoder.Encode(options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -76,7 +76,7 @@ class PointCloudKdTreeEncodingTest : public ::testing::Test { std::unique_ptr out_pc(new PointCloud()); DecoderOptions dec_options; - ASSERT_TRUE(decoder.Decode(dec_options, &dec_buffer, out_pc.get()).ok()); + DRACO_ASSERT_OK(decoder.Decode(dec_options, &dec_buffer, out_pc.get())); ComparePointClouds(pc, *out_pc); } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.cc index 8a0709678..8acd6687b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.cc @@ -20,11 +20,20 @@ BoundingBox::BoundingBox() : BoundingBox(Vector3f(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()), - Vector3f(-std::numeric_limits::max(), - -std::numeric_limits::max(), - -std::numeric_limits::max())) {} + Vector3f(std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest())) {} BoundingBox::BoundingBox(const Vector3f &min_point, const Vector3f &max_point) : min_point_(min_point), max_point_(max_point) {} +const bool BoundingBox::IsValid() const { + return GetMinPoint()[0] != std::numeric_limits::max() && + GetMinPoint()[1] != std::numeric_limits::max() && + GetMinPoint()[2] != std::numeric_limits::max() && + GetMaxPoint()[0] != std::numeric_limits::lowest() && + GetMaxPoint()[1] != std::numeric_limits::lowest() && + GetMaxPoint()[2] != std::numeric_limits::lowest(); +} + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.h b/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.h index 31ba2d683..697a73b6f 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/bounding_box.h @@ -38,6 +38,11 @@ class BoundingBox { // Returns the maximum point of the bounding box. inline const Vector3f &GetMaxPoint() const { return max_point_; } + // Checks if the bounding box object was created with the default constructor + // then never updated. Internally, checks if the bounding box minimum and + // maximum points hold the largest positive and smallest negative values. + const bool IsValid() const; + // Conditionally updates the bounding box with a given |new_point|. void Update(const Vector3f &new_point) { for (int i = 0; i < 3; i++) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/constants.h b/Engine/lib/assimp/contrib/draco/src/draco/core/constants.h new file mode 100644 index 000000000..3e81992a1 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/constants.h @@ -0,0 +1,6 @@ +#ifndef DRACO_CORE_CONSTANTS_H_ +#define DRACO_CORE_CONSTANTS_H_ + +#define DRACO_PI 3.14159265358979323846 + +#endif // DRACO_CORE_CONSTANTS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.cc index f0b43d67d..96a378798 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.cc @@ -52,7 +52,7 @@ void DataBuffer::Resize(int64_t size) { } void DataBuffer::WriteDataToStream(std::ostream &stream) { - if (data_.size() == 0) { + if (data_.empty()) { return; } stream.write(reinterpret_cast(data_.data()), data_.size()); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.h b/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.h index 8ee690540..8eac0f6b4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/data_buffer.h @@ -67,7 +67,7 @@ class DataBuffer { int64_t update_count() const { return descriptor_.buffer_update_count; } size_t data_size() const { return data_.size(); } const uint8_t *data() const { return data_.data(); } - uint8_t *data() { return &data_[0]; } + uint8_t *data() { return data_.data(); } int64_t buffer_id() const { return descriptor_.buffer_id; } void set_buffer_id(int64_t buffer_id) { descriptor_.buffer_id = buffer_id; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/decoder_buffer.h b/Engine/lib/assimp/contrib/draco/src/draco/core/decoder_buffer.h index 0559abbe4..71189b7e7 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/decoder_buffer.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/decoder_buffer.h @@ -54,12 +54,11 @@ class DecoderBuffer { // Decodes up to 32 bits into out_val. Can be called only in between // StartBitDecoding and EndBitDecoding. Otherwise returns false. - bool DecodeLeastSignificantBits32(int nbits, uint32_t *out_value) { + bool DecodeLeastSignificantBits32(uint32_t nbits, uint32_t *out_value) { if (!bit_decoder_active()) { return false; } - bit_decoder_.GetBits(nbits, out_value); - return true; + return bit_decoder_.GetBits(nbits, out_value); } // Decodes an arbitrary data type. @@ -158,11 +157,12 @@ class DecoderBuffer { inline void ConsumeBits(int k) { bit_offset_ += k; } // Returns |nbits| bits in |x|. - inline bool GetBits(int32_t nbits, uint32_t *x) { - DRACO_DCHECK_GE(nbits, 0); - DRACO_DCHECK_LE(nbits, 32); + inline bool GetBits(uint32_t nbits, uint32_t *x) { + if (nbits > 32) { + return false; + } uint32_t value = 0; - for (int32_t bit = 0; bit < nbits; ++bit) { + for (uint32_t bit = 0; bit < nbits; ++bit) { value |= GetBit() << bit; } *x = value; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_index_type_vector.h b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_index_type_vector.h index aae1e7aaf..f5256ded9 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_index_type_vector.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_index_type_vector.h @@ -25,25 +25,32 @@ namespace draco { // A wrapper around the standard std::vector that supports indexing of the // vector entries using the strongly typed indices as defined in -// draco_index_type.h . -// TODO(ostava): Make the interface more complete. It's currently missing -// features such as iterators. -// TODO(vytyaz): Add more unit tests for this class. +// draco_index_type.h. +// TODO(ostava): Make the interface more complete. It's currently missing some +// features. template class IndexTypeVector { public: typedef typename std::vector::const_reference const_reference; typedef typename std::vector::reference reference; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; IndexTypeVector() {} explicit IndexTypeVector(size_t size) : vector_(size) {} IndexTypeVector(size_t size, const ValueTypeT &val) : vector_(size, val) {} + iterator begin() { return vector_.begin(); } + const_iterator begin() const { return vector_.begin(); } + iterator end() { return vector_.end(); } + const_iterator end() const { return vector_.end(); } + void clear() { vector_.clear(); } void reserve(size_t size) { vector_.reserve(size); } void resize(size_t size) { vector_.resize(size); } void resize(size_t size, const ValueTypeT &val) { vector_.resize(size, val); } void assign(size_t size, const ValueTypeT &val) { vector_.assign(size, val); } + iterator erase(iterator position) { return vector_.erase(position); } void swap(IndexTypeVector &arg) { vector_.swap(arg.vector_); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.cc index edca9856d..a71082a86 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.cc @@ -16,9 +16,9 @@ #include +#include "draco/core/draco_test_base.h" #include "draco/core/macros.h" #include "draco/io/file_utils.h" -#include "draco_test_base.h" namespace draco { @@ -27,6 +27,8 @@ static constexpr char kTestDataDir[] = DRACO_TEST_DATA_DIR; static constexpr char kTestTempDir[] = DRACO_TEST_TEMP_DIR; } // namespace +std::string GetTestTempDir() { return std::string(kTestDataDir); } + std::string GetTestFileFullPath(const std::string &file_name) { return std::string(kTestDataDir) + std::string("/") + file_name; } @@ -55,11 +57,13 @@ bool CompareGoldenFile(const std::string &golden_file_name, const void *data, size_t remaining_data_size = data_size; int offset = 0; while ((extracted_size = in_file.read(buffer, buffer_size).gcount()) > 0) { - if (remaining_data_size <= 0) + if (remaining_data_size <= 0) { break; // Input and golden sizes are different. + } size_t size_to_check = extracted_size; - if (remaining_data_size < size_to_check) + if (remaining_data_size < size_to_check) { size_to_check = remaining_data_size; + } for (uint32_t i = 0; i < size_to_check; ++i) { if (buffer[i] != data_c8[offset++]) { LOG(INFO) << "Test output differed from golden file at byte " @@ -77,4 +81,20 @@ bool CompareGoldenFile(const std::string &golden_file_name, const void *data, return true; } +#ifdef DRACO_TRANSCODER_SUPPORTED + +template <> +std::unique_ptr ReadGeometryFromTestFile( + const std::string &file_name) { + return ReadMeshFromTestFile(file_name); +} + +template <> +std::unique_ptr ReadGeometryFromTestFile( + const std::string &file_name) { + return ReadSceneFromTestFile(file_name); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.h index fa548f52d..658096fe1 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_test_utils.h @@ -15,12 +15,24 @@ #ifndef DRACO_CORE_DRACO_TEST_UTILS_H_ #define DRACO_CORE_DRACO_TEST_UTILS_H_ +#include +#include +#include + #include "draco/core/draco_test_base.h" +#include "draco/draco_features.h" #include "draco/io/mesh_io.h" #include "draco/io/point_cloud_io.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/io/scene_io.h" +#endif + namespace draco { +// Returns test temporary directory. +std::string GetTestTempDir(); + // Returns the full path to a given file system entry, such as test file or test // directory. std::string GetTestFileFullPath(const std::string &entry_name); @@ -65,6 +77,47 @@ inline std::unique_ptr ReadPointCloudFromTestFile( return ReadPointCloudFromFile(path).value(); } +#ifdef DRACO_TRANSCODER_SUPPORTED +inline std::unique_ptr ReadSceneFromTestFile( + const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + return ReadSceneFromFile(path).value(); +} + +// Loads geometry specified by a |file_name| that is going to be automatically +// converted to the correct path available to the testing instance. Supported +// geometry types are Mesh and Scene. +template +std::unique_ptr ReadGeometryFromTestFile(const std::string &file_name); + +#endif // DRACO_TRANSCODER_SUPPORTED + +// Utility class for redirection and capture of stderr/stdout. +class CaptureStream { + public: + explicit CaptureStream(std::ostream &stream) + : old_buffer_(stream.rdbuf(buffer_.rdbuf())), stream_(stream) {} + + ~CaptureStream() { Reset(); } + + std::string GetStringAndRelease() { + Reset(); + return buffer_.str(); + } + + void Reset() { + if (old_buffer_) { + stream_.rdbuf(old_buffer_); + old_buffer_ = nullptr; + } + } + + private: + std::ostringstream buffer_; + std::streambuf *old_buffer_ = nullptr; + std::ostream &stream_; +}; + // Evaluates an expression that returns draco::Status. If the status is not OK, // the macro asserts and logs the error message. #define DRACO_ASSERT_OK(expression) \ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_version.h b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_version.h index 14a504a50..88856447f 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/draco_version.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/draco_version.h @@ -18,9 +18,7 @@ namespace draco { // Draco version is comprised of ... -static const char kDracoVersion[] = "1.4.1"; - -const char *Version() { return kDracoVersion; } +static const char kDracoVersion[] = "1.5.6"; } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/macros.h b/Engine/lib/assimp/contrib/draco/src/draco/core/macros.h index 147bbaafc..a31e7c44b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/macros.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/macros.h @@ -15,7 +15,8 @@ #ifndef DRACO_CORE_MACROS_H_ #define DRACO_CORE_MACROS_H_ -#include "assert.h" +#include + #include "draco/draco_features.h" #ifdef ANDROID_LOGGING @@ -37,7 +38,7 @@ namespace draco { #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName &) = delete; \ void operator=(const TypeName &) = delete; -#endif +#endif // DISALLOW_COPY_AND_ASSIGN #ifndef FALLTHROUGH_INTENDED #if defined(__clang__) && defined(__has_warning) @@ -46,7 +47,7 @@ namespace draco { #endif #elif defined(__GNUC__) && __GNUC__ >= 7 #define FALLTHROUGH_INTENDED [[gnu::fallthrough]] -#endif +#endif // FALLTHROUGH_INTENDED // If FALLTHROUGH_INTENDED is still not defined, define it. #ifndef FALLTHROUGH_INTENDED @@ -54,7 +55,7 @@ namespace draco { do { \ } while (0) #endif -#endif +#endif // FALLTHROUGH_INTENDED #ifndef LOG #define LOG(...) std::cout @@ -84,12 +85,16 @@ namespace draco { #define DRACO_DCHECK_LE(a, b) #define DRACO_DCHECK_LT(a, b) #define DRACO_DCHECK_NOTNULL(x) -#endif +#endif // DRACO_DEBUG // Helper macros for concatenating macro values. #define DRACO_MACROS_IMPL_CONCAT_INNER_(x, y) x##y #define DRACO_MACROS_IMPL_CONCAT_(x, y) DRACO_MACROS_IMPL_CONCAT_INNER_(x, y) +#define DRACO_MACROS_IMPL_CONCAT_INNER_3_(x, y, z) x##y##z +#define DRACO_MACROS_IMPL_CONCAT_3_(x, y, z) \ + DRACO_MACROS_IMPL_CONCAT_INNER_3_(x, y, z) + // Expand the n-th argument of the macro. Used to select an argument based on // the number of entries in a variadic macro argument. Example usage: // @@ -100,9 +105,9 @@ namespace draco { // #define VARIADIC_MACRO(...) // DRACO_SELECT_NTH_FROM_3(__VA_ARGS__, FUNC_3, FUNC_2, FUNC_1) __VA_ARGS__ // -#define DRACO_SELECT_NTH_FROM_2(_1, _2, NAME) NAME -#define DRACO_SELECT_NTH_FROM_3(_1, _2, _3, NAME) NAME -#define DRACO_SELECT_NTH_FROM_4(_1, _2, _3, _4, NAME) NAME +#define DRACO_SELECT_NTH_FROM_2(_1, _2, NAME, ...) NAME +#define DRACO_SELECT_NTH_FROM_3(_1, _2, _3, NAME, ...) NAME +#define DRACO_SELECT_NTH_FROM_4(_1, _2, _3, _4, NAME, ...) NAME // Macro that converts the Draco bit-stream into one uint16_t number. // Useful mostly when checking version numbers. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils.h index 7f382fa34..d7732e55d 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils.h @@ -19,6 +19,8 @@ #include "draco/core/vector_d.h" +namespace draco { + #define DRACO_INCREMENT_MOD(I, M) (((I) == ((M)-1)) ? 0 : ((I) + 1)) // Returns floor(sqrt(x)) where x is an integer number. The main intend of this @@ -52,4 +54,26 @@ inline uint64_t IntSqrt(uint64_t number) { return square_root; } +// Performs the addition in unsigned type to avoid signed integer overflow. Note +// that the result will be the same (for non-overflowing values). +template < + typename DataTypeT, + typename std::enable_if::value && + std::is_signed::value>::type * = nullptr> +inline DataTypeT AddAsUnsigned(DataTypeT a, DataTypeT b) { + typedef typename std::make_unsigned::type DataTypeUT; + return static_cast(static_cast(a) + + static_cast(b)); +} + +template ::value || + !std::is_signed::value>::type * = + nullptr> +inline DataTypeT AddAsUnsigned(DataTypeT a, DataTypeT b) { + return a + b; +} + +} // namespace draco + #endif // DRACO_CORE_MATH_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils_test.cc index 8c255d046..460a67444 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/math_utils_test.cc @@ -5,7 +5,7 @@ #include "draco/core/draco_test_base.h" -using draco::Vector3f; +namespace draco { TEST(MathUtils, Mod) { EXPECT_EQ(DRACO_INCREMENT_MOD(1, 1 << 1), 0); } @@ -20,3 +20,5 @@ TEST(MathUtils, IntSqrt) { ASSERT_EQ(IntSqrt(number), static_cast(floor(std::sqrt(number)))); } } + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/options.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/options.cc index 9b81db489..ceb87cccc 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/options.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/options.cc @@ -15,13 +15,12 @@ #include "draco/core/options.h" #include +#include #include #include namespace draco { -Options::Options() {} - void Options::MergeAndReplace(const Options &other_options) { for (const auto &item : other_options.options_) { options_[item.first] = item.second; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/options.h b/Engine/lib/assimp/contrib/draco/src/draco/core/options.h index 1bc4dc0fb..3f15d13ba 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/options.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/options.h @@ -19,6 +19,8 @@ #include #include +#include "draco/draco_features.h" + namespace draco { // Class for storing generic options as a pair in a string map. @@ -27,7 +29,8 @@ namespace draco { // data type. class Options { public: - Options(); + Options() = default; + ~Options() = default; // Merges |other_options| on top of the existing options of this instance // replacing all entries that are present in both options instances. @@ -71,8 +74,6 @@ class Options { private: // All entries are internally stored as strings and converted to the desired // return type based on the used Get* method. - // TODO(ostava): Consider adding type safety mechanism that would prevent - // unsafe operations such as a conversion from vector to int. std::map options_; }; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/status.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/status.cc new file mode 100644 index 000000000..ecb0b536e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/status.cc @@ -0,0 +1,44 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "draco/core/status.h" + +#include + +namespace draco { + +std::string Status::code_string() const { + switch (code_) { + case Code::OK: + return "OK"; + case Code::DRACO_ERROR: + return "DRACO_ERROR"; + case Code::IO_ERROR: + return "IO_ERROR"; + case Code::INVALID_PARAMETER: + return "INVALID_PARAMETER"; + case Code::UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case Code::UNKNOWN_VERSION: + return "UNKNOWN_VERSION"; + case Code::UNSUPPORTED_FEATURE: + return "UNSUPPORTED_FEATURE"; + } + return "UNKNOWN_STATUS_VALUE"; +} + +std::string Status::code_and_error_string() const { + return code_string() + ": " + error_msg_string(); +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/status.h b/Engine/lib/assimp/contrib/draco/src/draco/core/status.h index 449ad8566..fac96046c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/status.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/status.h @@ -15,6 +15,7 @@ #ifndef DRACO_CORE_STATUS_H_ #define DRACO_CORE_STATUS_H_ +#include #include namespace draco { @@ -44,6 +45,8 @@ class Status { Code code() const { return code_; } const std::string &error_msg_string() const { return error_msg_; } const char *error_msg() const { return error_msg_.c_str(); } + std::string code_string() const; + std::string code_and_error_string() const; bool operator==(Code code) const { return code == code_; } bool ok() const { return code_ == OK; } @@ -61,6 +64,9 @@ inline std::ostream &operator<<(std::ostream &os, const Status &status) { } inline Status OkStatus() { return Status(Status::OK); } +inline Status ErrorStatus(const std::string &msg) { + return Status(Status::DRACO_ERROR, msg); +} // Evaluates an expression that returns draco::Status. If the status is not OK, // the macro returns the status object. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/status_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/status_test.cc index c1ad4ab30..dc36496d4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/status_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/status_test.cc @@ -29,10 +29,17 @@ TEST_F(StatusTest, TestStatusOutput) { // Tests that the Status can be stored in a provided std::ostream. const draco::Status status(draco::Status::DRACO_ERROR, "Error msg."); ASSERT_EQ(status.code(), draco::Status::DRACO_ERROR); + ASSERT_EQ(status.code_string(), "DRACO_ERROR"); std::stringstream str; str << status; ASSERT_EQ(str.str(), "Error msg."); + + const draco::Status status2 = draco::ErrorStatus("Error msg2."); + ASSERT_EQ(status2.code(), draco::Status::DRACO_ERROR); + ASSERT_EQ(status2.error_msg_string(), "Error msg2."); + ASSERT_EQ(status2.code_string(), "DRACO_ERROR"); + ASSERT_EQ(status2.code_and_error_string(), "DRACO_ERROR: Error msg2."); } } // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d.h b/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d.h index a3c46a46a..a0ec2dedf 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d.h @@ -34,7 +34,7 @@ class VectorD { typedef ScalarT Scalar; typedef VectorD Self; - // TODO(hemmer): Deprecate. + // TODO(b/199760123): Deprecate. typedef ScalarT CoefficientType; VectorD() { @@ -45,7 +45,7 @@ class VectorD { // The following constructor does not compile in opt mode, which for now led // to the constructors further down, which is not ideal. - // TODO(hemmer): fix constructor below and remove others. + // TODO(b/199760123): Fix constructor below and remove others. // template // explicit VectorD(Args... args) : v_({args...}) {} @@ -111,7 +111,7 @@ class VectorD { Scalar &operator[](int i) { return v_[i]; } const Scalar &operator[](int i) const { return v_[i]; } - // TODO(hemmer): remove. + // TODO(b/199760123): Remove. // Similar to interface of Eigen library. Scalar &operator()(int i) { return v_[i]; } const Scalar &operator()(int i) const { return v_[i]; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d_test.cc index d66128fb1..21c1ca4c5 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/core/vector_d_test.cc @@ -32,16 +32,6 @@ typedef draco::Vector5ui Vector5ui; typedef draco::VectorD Vector3i; typedef draco::VectorD Vector4i; -template -void TestSquaredDistance(const draco::VectorD v1, - const draco::VectorD v2, - const CoeffT result) { - CoeffT squared_distance = SquaredDistance(v1, v2); - ASSERT_EQ(squared_distance, result); - squared_distance = SquaredDistance(v2, v1); - ASSERT_EQ(squared_distance, result); -} - TEST(VectorDTest, TestOperators) { { const Vector3f v; @@ -170,56 +160,6 @@ TEST(VectorTest, TestGetNormalizedWithZeroLengthVector) { ASSERT_EQ(normalized[2], 0); } -TEST(VectorDTest, TestSquaredDistance) { - // Test Vector2f: float, 2D. - Vector2f v1_2f(5.5, 10.5); - Vector2f v2_2f(3.5, 15.5); - float result_f = 29; - TestSquaredDistance(v1_2f, v2_2f, result_f); - - // Test Vector3f: float, 3D. - Vector3f v1_3f(5.5, 10.5, 2.3); - Vector3f v2_3f(3.5, 15.5, 0); - result_f = 34.29; - TestSquaredDistance(v1_3f, v2_3f, result_f); - - // Test Vector4f: float, 4D. - Vector4f v1_4f(5.5, 10.5, 2.3, 7.2); - Vector4f v2_4f(3.5, 15.5, 0, 9.9); - result_f = 41.58; - TestSquaredDistance(v1_4f, v2_4f, result_f); - - // Test Vector5f: float, 5D. - Vector5f v1_5f(5.5, 10.5, 2.3, 7.2, 1.0); - Vector5f v2_5f(3.5, 15.5, 0, 9.9, 0.2); - result_f = 42.22; - TestSquaredDistance(v1_5f, v2_5f, result_f); - - // Test Vector 2ui: uint32_t, 2D. - Vector2ui v1_2ui(5, 10); - Vector2ui v2_2ui(3, 15); - uint32_t result_ui = 29; - TestSquaredDistance(v1_2ui, v2_2ui, result_ui); - - // Test Vector 3ui: uint32_t, 3D. - Vector3ui v1_3ui(5, 10, 2); - Vector3ui v2_3ui(3, 15, 0); - result_ui = 33; - TestSquaredDistance(v1_3ui, v2_3ui, result_ui); - - // Test Vector 4ui: uint32_t, 4D. - Vector4ui v1_4ui(5, 10, 2, 7); - Vector4ui v2_4ui(3, 15, 0, 9); - result_ui = 37; - TestSquaredDistance(v1_4ui, v2_4ui, result_ui); - - // Test Vector 5ui: uint32_t, 5D. - Vector5ui v1_5ui(5, 10, 2, 7, 1); - Vector5ui v2_5ui(3, 15, 0, 9, 12); - result_ui = 158; - TestSquaredDistance(v1_5ui, v2_5ui, result_ui); -} - TEST(VectorDTest, TestCrossProduct3D) { const Vector3i e1(1, 0, 0); const Vector3i e2(0, 1, 0); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_reader_factory.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_reader_factory.cc index ac7b09288..a8f15a11f 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_reader_factory.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_reader_factory.cc @@ -1,5 +1,6 @@ #include "draco/io/file_reader_factory.h" +#include #include namespace draco { @@ -38,7 +39,6 @@ std::unique_ptr FileReaderFactory::OpenReader( } return reader; } - FILEREADER_LOG_ERROR("No file reader able to open input"); return nullptr; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.cc index f93cbd899..694d259e8 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.cc @@ -14,6 +14,8 @@ // #include "draco/io/file_utils.h" +#include + #include "draco/io/file_reader_factory.h" #include "draco/io/file_reader_interface.h" #include "draco/io/file_writer_factory.h" @@ -30,7 +32,7 @@ void SplitPath(const std::string &full_path, std::string *out_folder_path, std::string ReplaceFileExtension(const std::string &in_file_name, const std::string &new_extension) { - const auto pos = in_file_name.find_last_of("."); + const auto pos = in_file_name.find_last_of('.'); if (pos == std::string::npos) { // No extension found. return in_file_name + "." + new_extension; @@ -46,6 +48,22 @@ std::string LowercaseFileExtension(const std::string &filename) { return parser::ToLower(filename.substr(pos + 1)); } +std::string LowercaseMimeTypeExtension(const std::string &mime_type) { + const size_t pos = mime_type.find_last_of('/'); + if (pos == 0 || pos == std::string::npos || pos == mime_type.length() - 1) { + return ""; + } + return parser::ToLower(mime_type.substr(pos + 1)); +} + +std::string RemoveFileExtension(const std::string &filename) { + const size_t pos = filename.find_last_of('.'); + if (pos == 0 || pos == std::string::npos || pos == filename.length() - 1) { + return filename; + } + return filename.substr(0, pos); +} + std::string GetFullPath(const std::string &input_file_relative_path, const std::string &sibling_file_full_path) { const auto pos = sibling_file_full_path.find_last_of("/\\"); @@ -76,6 +94,23 @@ bool ReadFileToBuffer(const std::string &file_name, return file_reader->ReadFileToBuffer(buffer); } +bool ReadFileToString(const std::string &file_name, std::string *contents) { + if (!contents) { + return false; + } + std::unique_ptr file_reader = + FileReaderFactory::OpenReader(file_name); + if (file_reader == nullptr) { + return false; + } + std::vector buffer; + if (!ReadFileToBuffer(file_name, &buffer)) { + return false; + } + contents->assign(buffer.begin(), buffer.end()); + return true; +} + bool WriteBufferToFile(const char *buffer, size_t buffer_size, const std::string &file_name) { std::unique_ptr file_writer = diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.h index 4b734e049..7a5fc475b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils.h @@ -15,6 +15,7 @@ #ifndef DRACO_IO_FILE_UTILS_H_ #define DRACO_IO_FILE_UTILS_H_ +#include #include #include @@ -37,6 +38,13 @@ std::string ReplaceFileExtension(const std::string &in_file_name, // '.' (e.g. Linux hidden files), the first delimiter is ignored. std::string LowercaseFileExtension(const std::string &filename); +// Returns the mime type extension in lowercase if present, else "". Extension +// is defined as the string after the last '/ character. +std::string LowercaseMimeTypeExtension(const std::string &mime_type); + +// Returns the file name without extension. +std::string RemoveFileExtension(const std::string &filename); + // Given a path of the input file |input_file_relative_path| relative to the // parent directory of |sibling_file_full_path|, this function returns full path // to the input file. If |sibling_file_full_path| has no directory, the relative @@ -46,13 +54,18 @@ std::string LowercaseFileExtension(const std::string &filename); std::string GetFullPath(const std::string &input_file_relative_path, const std::string &sibling_file_full_path); -// Convenience method. Uses draco::FileReaderFactory internally. Reads contents +// Convenience methods. Uses draco::FileReaderFactory internally. Reads contents // of file referenced by |file_name| into |buffer| and returns true upon // success. bool ReadFileToBuffer(const std::string &file_name, std::vector *buffer); bool ReadFileToBuffer(const std::string &file_name, std::vector *buffer); +// Convenience method for reading a file into a std::string. Reads contents +// of file referenced by |file_name| into |contents| and returns true upon +// success. +bool ReadFileToString(const std::string &file_name, std::string *contents); + // Convenience method. Uses draco::FileWriterFactory internally. Writes contents // of |buffer| to file referred to by |file_name|. File is overwritten if it // exists. Returns true after successful write. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils_test.cc index 4085ff0cd..b55b1e3be 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_utils_test.cc @@ -14,6 +14,8 @@ // #include "draco/io/file_utils.h" +#include + #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_factory.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_factory.cc index cb6851602..8ffd5400a 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_factory.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_factory.cc @@ -1,5 +1,6 @@ #include "draco/io/file_writer_factory.h" +#include #include namespace draco { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils.cc index bcadccfc6..3dab80bbf 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils.cc @@ -7,6 +7,10 @@ #include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "ghc/filesystem.hpp" +#endif // DRACO_TRANSCODER_SUPPORTED + namespace draco { void SplitPathPrivate(const std::string &full_path, @@ -30,8 +34,18 @@ void SplitPathPrivate(const std::string &full_path, } } -bool DirectoryExists(const std::string &path) { +bool DirectoryExists(const std::string &path_arg) { struct stat path_stat; + std::string path = path_arg; + +#if defined(_WIN32) && not defined(__MINGW32__) + // Avoid a silly windows issue: stat() will fail on a drive letter missing the + // trailing slash. + if (path.size() > 0 && path[path.size()] != '\\' && + path[path.size()] != '/') { + path.append("\\"); + } +#endif // Check if |path| exists. if (stat(path.c_str(), &path_stat) != 0) { @@ -50,7 +64,12 @@ bool CheckAndCreatePathForFile(const std::string &filename) { std::string basename; SplitPathPrivate(filename, &path, &basename); +#ifdef DRACO_TRANSCODER_SUPPORTED + const ghc::filesystem::path ghc_path(path); + ghc::filesystem::create_directories(ghc_path); +#endif // DRACO_TRANSCODER_SUPPORTED const bool directory_exists = DirectoryExists(path); + return directory_exists; } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils_test.cc new file mode 100644 index 000000000..14a3013e0 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/file_writer_utils_test.cc @@ -0,0 +1,49 @@ +#include "draco/io/file_writer_utils.h" + +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { +namespace { + +TEST(FileWriterUtilsTest, SplitPathPrivateNonWindows) { + const std::string test_path = "/path/to/file"; + std::string directory; + std::string file; + SplitPathPrivate(test_path, &directory, &file); + ASSERT_EQ(directory, "/path/to"); + ASSERT_EQ(file, "file"); +} + +TEST(FileWriterUtilsTest, SplitPathPrivateWindows) { + const std::string test_path = "C:\\path\\to\\file"; + std::string directory; + std::string file; + SplitPathPrivate(test_path, &directory, &file); + ASSERT_EQ(directory, "C:\\path\\to"); + ASSERT_EQ(file, "file"); +} + +TEST(FileWriterUtilsTest, DirectoryExistsTest) { + ASSERT_TRUE(DirectoryExists(GetTestTempDir())); + ASSERT_FALSE(DirectoryExists("fake/test/subdir")); +} + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(FileWriterUtilsTest, CheckAndCreatePathForFileTest) { + const std::string fake_file = "fake.file"; + const std::string fake_file_subdir = "a/few/dirs/down"; + const std::string test_temp_dir = GetTestTempDir(); + const std::string fake_file_directory = + test_temp_dir + "/" + fake_file_subdir; + const std::string fake_full_path = + test_temp_dir + "/" + fake_file_subdir + "/" + fake_file; + ASSERT_TRUE(CheckAndCreatePathForFile(fake_full_path)); + ASSERT_TRUE(DirectoryExists(fake_file_directory)); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.cc new file mode 100644 index 000000000..521b7524f --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.cc @@ -0,0 +1,2893 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_decoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include +#include + +#include "draco/core/draco_types.h" +#include "draco/core/hash_utils.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/tiny_gltf_utils.h" +#include "draco/material/material_library.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/metadata/property_table.h" +#include "draco/point_cloud/point_cloud_builder.h" +#include "draco/scene/scene_indices.h" +#include "draco/texture/source_image.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +draco::DataType GltfComponentTypeToDracoType(int component_type) { + switch (component_type) { + case TINYGLTF_COMPONENT_TYPE_BYTE: + return DT_INT8; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + return DT_UINT8; + case TINYGLTF_COMPONENT_TYPE_SHORT: + return DT_INT16; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + return DT_UINT16; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + return DT_UINT32; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + return DT_FLOAT32; + } + return DT_INVALID; +} + +GeometryAttribute::Type GltfAttributeToDracoAttribute( + const std::string attribute_name) { + if (attribute_name == "POSITION") { + return GeometryAttribute::POSITION; + } else if (attribute_name == "NORMAL") { + return GeometryAttribute::NORMAL; + } else if (attribute_name == "TEXCOORD_0") { + return GeometryAttribute::TEX_COORD; + } else if (attribute_name == "TEXCOORD_1") { + return GeometryAttribute::TEX_COORD; + } else if (attribute_name == "TANGENT") { + return GeometryAttribute::TANGENT; + } else if (attribute_name == "COLOR_0") { + return GeometryAttribute::COLOR; + } else if (attribute_name == "JOINTS_0") { + return GeometryAttribute::JOINTS; + } else if (attribute_name == "WEIGHTS_0") { + return GeometryAttribute::WEIGHTS; + } else if (attribute_name.rfind("_FEATURE_ID_") == 0) { + // Feature ID attribute like _FEATURE_ID_5 from EXT_mesh_features extension. + return GeometryAttribute::GENERIC; + } + return GeometryAttribute::INVALID; +} + +StatusOr TinyGltfToDracoAxisWrappingMode( + int wrap_mode) { + switch (wrap_mode) { + case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: + return TextureMap::CLAMP_TO_EDGE; + case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT: + return TextureMap::MIRRORED_REPEAT; + case TINYGLTF_TEXTURE_WRAP_REPEAT: + return TextureMap::REPEAT; + default: + return Status(Status::UNSUPPORTED_FEATURE, "Unsupported wrapping mode."); + } +} + +StatusOr TinyGltfToDracoFilterType(int filter_type) { + switch (filter_type) { + case -1: + return TextureMap::UNSPECIFIED; + case TINYGLTF_TEXTURE_FILTER_NEAREST: + return TextureMap::NEAREST; + case TINYGLTF_TEXTURE_FILTER_LINEAR: + return TextureMap::LINEAR; + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: + return TextureMap::NEAREST_MIPMAP_NEAREST; + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: + return TextureMap::LINEAR_MIPMAP_NEAREST; + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: + return TextureMap::NEAREST_MIPMAP_LINEAR; + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: + return TextureMap::LINEAR_MIPMAP_LINEAR; + default: + return Status(Status::DRACO_ERROR, "Unsupported texture filter type."); + } +} + +StatusOr> CopyDataAsUint32( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return Status(Status::DRACO_ERROR, "Byte cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return Status(Status::DRACO_ERROR, "Short cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return Status(Status::DRACO_ERROR, "Int cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, "Float cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return Status(Status::DRACO_ERROR, "Double cannot be converted to Uint32."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, + "Error CopyDataAsUint32() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAsUint32() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + const int num_elements = accessor.count * num_components; + + std::vector output; + output.resize(num_elements); + + int out_index = 0; + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + for (int c = 0; c < num_components; ++c) { + uint32_t value = 0; + memcpy(&value, data + (c * component_size), component_size); + output[out_index++] = value; + } + + data += byte_stride; + } + + return output; +} + +// Specialization for arithmetic types. +template < + typename TypeT, + typename std::enable_if::value>::type * = nullptr> +StatusOr> CopyDataAs(const tinygltf::Model &model, + const tinygltf::Accessor &accessor) { + if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint8."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint16."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint32."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_FLOAT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Float."); + } + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + int out_index = 0; + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + for (int c = 0; c < num_components; ++c) { + TypeT value = 0; + memcpy(&value, data + (c * component_size), component_size); + output[out_index++] = value; + } + data += byte_stride; + } + return output; +} + +// Specialization for remaining types is used for draco::VectorD. +template ::value>::type * = + nullptr> +StatusOr> CopyDataAs(const tinygltf::Model &model, + const tinygltf::Accessor &accessor) { + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + if (num_components != TypeT::dimension) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + TypeT values; + for (int c = 0; c < num_components; ++c) { + memcpy(&values[c], data + (c * component_size), component_size); + } + output[i] = values; + data += byte_stride; + } + return output; +} + +// Copies the data referenced from |buffer_view_id| into |data|. Currently only +// supports a byte stride of 0. I.e. tightly packed. +Status CopyDataFromBufferView(const tinygltf::Model &model, int buffer_view_id, + std::vector *data) { + if (buffer_view_id < 0) { + return ErrorStatus("Error CopyDataFromBufferView() bufferView < 0."); + } + const tinygltf::BufferView &buffer_view = model.bufferViews[buffer_view_id]; + if (buffer_view.buffer < 0) { + return ErrorStatus("Error CopyDataFromBufferView() buffer < 0."); + } + if (buffer_view.byteStride != 0) { + return Status(Status::DRACO_ERROR, "Error buffer view byteStride != 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + const uint8_t *const data_start = buffer.data.data() + buffer_view.byteOffset; + + data->resize(buffer_view.byteLength); + memcpy(&(*data)[0], data_start, buffer_view.byteLength); + return OkStatus(); +} + +// Returns a SourceImage created from |image|. +StatusOr> GetSourceImage( + const tinygltf::Model &model, const tinygltf::Image &image, + const Texture &texture) { + std::unique_ptr source_image(new SourceImage()); + // If the image is in an external file then the buffer view is < 0. + if (image.bufferView >= 0) { + DRACO_RETURN_IF_ERROR(CopyDataFromBufferView( + model, image.bufferView, &source_image->MutableEncodedData())); + } + source_image->set_filename(image.uri); + source_image->set_mime_type(image.mimeType); + + return source_image; +} + +std::unique_ptr GetNodeTrsMatrix(const tinygltf::Node &node) { + std::unique_ptr trsm(new TrsMatrix()); + if (node.matrix.size() == 16) { + Eigen::Matrix4d transformation; + // clang-format off + // |node.matrix| is in the column-major order. + transformation << + node.matrix[0], node.matrix[4], node.matrix[8], node.matrix[12], + node.matrix[1], node.matrix[5], node.matrix[9], node.matrix[13], + node.matrix[2], node.matrix[6], node.matrix[10], node.matrix[14], + node.matrix[3], node.matrix[7], node.matrix[11], node.matrix[15]; + // clang-format on + if (transformation != Eigen::Matrix4d::Identity()) { + trsm->SetMatrix(transformation); + } + } + + if (node.translation.size() == 3) { + const Eigen::Vector3d default_translation(0.0, 0.0, 0.0); + const Eigen::Vector3d node_translation( + node.translation[0], node.translation[1], node.translation[2]); + if (node_translation != default_translation) { + trsm->SetTranslation(node_translation); + } + } + if (node.scale.size() == 3) { + const Eigen::Vector3d default_scale(1.0, 1.0, 1.0); + const Eigen::Vector3d node_scale(node.scale[0], node.scale[1], + node.scale[2]); + if (node_scale != default_scale) { + trsm->SetScale(node_scale); + } + } + if (node.rotation.size() == 4) { + // Eigen quaternion is defined in (w, x, y, z) vs glTF that uses + // (x, y, z, w). + const Eigen::Quaterniond default_rotation(0.0, 0.0, 0.0, 1.0); + const Eigen::Quaterniond node_rotation(node.rotation[3], node.rotation[0], + node.rotation[1], node.rotation[2]); + if (node_rotation != default_rotation) { + trsm->SetRotation(node_rotation); + } + } + + return trsm; +} + +Eigen::Matrix4d UpdateMatrixForNormals( + const Eigen::Matrix4d &transform_matrix) { + Eigen::Matrix3d mat3x3; + // clang-format off + mat3x3 << + transform_matrix(0, 0), transform_matrix(0, 1), transform_matrix(0, 2), + transform_matrix(1, 0), transform_matrix(1, 1), transform_matrix(1, 2), + transform_matrix(2, 0), transform_matrix(2, 1), transform_matrix(2, 2); + // clang-format on + + mat3x3 = mat3x3.inverse().transpose(); + Eigen::Matrix4d mat4x4; + // clang-format off + mat4x4 << mat3x3(0, 0), mat3x3(0, 1), mat3x3(0, 2), 0.0, + mat3x3(1, 0), mat3x3(1, 1), mat3x3(1, 2), 0.0, + mat3x3(2, 0), mat3x3(2, 1), mat3x3(2, 2), 0.0, + 0.0, 0.0, 0.0, 1.0; + // clang-format on + return mat4x4; +} + +float Determinant(const Eigen::Matrix4d &transform_matrix) { + Eigen::Matrix3d mat3x3; + // clang-format off + mat3x3 << + transform_matrix(0, 0), transform_matrix(0, 1), transform_matrix(0, 2), + transform_matrix(1, 0), transform_matrix(1, 1), transform_matrix(1, 2), + transform_matrix(2, 0), transform_matrix(2, 1), transform_matrix(2, 2); + // clang-format on + return mat3x3.determinant(); +} + +bool FileExists(const std::string &filepath, void * /*user_data*/) { + return GetFileSize(filepath) != 0; +} + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *user_data) { + if (!ReadFileToBuffer(filepath, out)) { + if (err) { + *err = "Unable to read: " + filepath; + } + return false; + } + if (user_data) { + auto *files_vector = + reinterpret_cast *>(user_data); + files_vector->push_back(filepath); + } + return true; +} + +bool WriteWholeFile(std::string * /*err*/, const std::string &filepath, + const std::vector &contents, + void * /*user_data*/) { + return WriteBufferToFile(contents.data(), contents.size(), filepath); +} + +} // namespace + +GltfDecoder::GltfDecoder() + : next_face_id_(0), + next_point_id_(0), + total_face_indices_count_(0), + total_point_indices_count_(0), + material_att_id_(-1) {} + +StatusOr> GltfDecoder::DecodeFromFile( + const std::string &file_name) { + return DecodeFromFile(file_name, nullptr); +} + +StatusOr> GltfDecoder::DecodeFromFile( + const std::string &file_name, std::vector *mesh_files) { + DRACO_RETURN_IF_ERROR(LoadFile(file_name, mesh_files)); + return BuildMesh(); +} + +StatusOr> GltfDecoder::DecodeFromBuffer( + DecoderBuffer *buffer) { + DRACO_RETURN_IF_ERROR(LoadBuffer(*buffer)); + return BuildMesh(); +} + +StatusOr> GltfDecoder::DecodeFromFileToScene( + const std::string &file_name) { + return DecodeFromFileToScene(file_name, nullptr); +} + +StatusOr> GltfDecoder::DecodeFromFileToScene( + const std::string &file_name, std::vector *scene_files) { + DRACO_RETURN_IF_ERROR(LoadFile(file_name, scene_files)); + scene_ = std::unique_ptr(new Scene()); + DRACO_RETURN_IF_ERROR(DecodeGltfToScene()); + return std::move(scene_); +} + +StatusOr> GltfDecoder::DecodeFromBufferToScene( + DecoderBuffer *buffer) { + DRACO_RETURN_IF_ERROR(LoadBuffer(*buffer)); + scene_ = std::unique_ptr(new Scene()); + DRACO_RETURN_IF_ERROR(DecodeGltfToScene()); + return std::move(scene_); +} + +Status GltfDecoder::LoadFile(const std::string &file_name, + std::vector *input_files) { + const std::string extension = LowercaseFileExtension(file_name); + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + const tinygltf::FsCallbacks fs_callbacks = { + &FileExists, + // TinyGLTF's ExpandFilePath does not do filesystem i/o, so it's safe to + // use in all environments. + &tinygltf::ExpandFilePath, &ReadWholeFile, &WriteWholeFile, + reinterpret_cast(input_files)}; + + loader.SetFsCallbacks(fs_callbacks); + + if (extension == "glb") { + if (!loader.LoadBinaryFromFile(&gltf_model_, &err, &warn, file_name)) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glb file: " + err); + } + } else if (extension == "gltf") { + if (!loader.LoadASCIIFromFile(&gltf_model_, &err, &warn, file_name)) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glTF file: " + err); + } + } else { + return Status(Status::DRACO_ERROR, "Unknown input file extension."); + } + DRACO_RETURN_IF_ERROR(CheckUnsupportedFeatures()); + input_file_name_ = file_name; + return OkStatus(); +} + +Status GltfDecoder::LoadBuffer(const DecoderBuffer &buffer) { + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + if (!loader.LoadBinaryFromMemory( + &gltf_model_, &err, &warn, + reinterpret_cast(buffer.data_head()), + buffer.remaining_size())) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glb buffer: " + err); + } + DRACO_RETURN_IF_ERROR(CheckUnsupportedFeatures()); + input_file_name_.clear(); + return OkStatus(); +} + +StatusOr> GltfDecoder::BuildMesh() { + DRACO_RETURN_IF_ERROR(GatherAttributeAndMaterialStats()); + if (total_face_indices_count_ > 0 && total_point_indices_count_ > 0) { + return ErrorStatus( + "Decoding to mesh can't handle triangle and point primitives at the " + "same time."); + } + if (total_face_indices_count_ > 0) { + mb_.Start(total_face_indices_count_ / 3); + DRACO_RETURN_IF_ERROR(AddAttributesToDracoMesh(&mb_)); + } else { + pb_.Start(total_point_indices_count_); + DRACO_RETURN_IF_ERROR(AddAttributesToDracoMesh(&pb_)); + } + + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + const Eigen::Matrix4d parent_matrix = Eigen::Matrix4d::Identity(); + DRACO_RETURN_IF_ERROR(DecodeNode(scene.nodes[i], parent_matrix)); + } + } + DRACO_ASSIGN_OR_RETURN( + std::unique_ptr mesh, + BuildMeshFromBuilder(total_face_indices_count_ > 0, &mb_, &pb_)); + + DRACO_RETURN_IF_ERROR(CopyTextures(mesh.get())); + SetAttributePropertiesOnDracoMesh(mesh.get()); + DRACO_RETURN_IF_ERROR(AddMaterialsToDracoMesh(mesh.get())); + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(mesh.get())); + DRACO_RETURN_IF_ERROR(AddStructuralMetadataToGeometry(mesh.get())); + MoveNonMaterialTextures(mesh.get()); + return mesh; +} + +Status GltfDecoder::AddMeshFeaturesToDracoMesh(Mesh *mesh) { + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(scene.nodes[i], mesh)); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddMeshFeaturesToDracoMesh(int node_index, Mesh *mesh) { + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + if (node.mesh >= 0) { + const tinygltf::Mesh &gltf_mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : gltf_mesh.primitives) { + // Decode mesh feature ID sets if present in this primitive. + DRACO_RETURN_IF_ERROR(DecodeMeshFeatures( + primitive, &mesh->GetMaterialLibrary().MutableTextureLibrary(), + mesh)); + } + } + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(node.children[i], mesh)); + } + return OkStatus(); +} + +Status GltfDecoder::CheckUnsupportedFeatures() { + // Check for morph targets. + for (const auto &mesh : gltf_model_.meshes) { + for (const auto &primitive : mesh.primitives) { + if (!primitive.targets.empty()) { + return Status(Status::UNSUPPORTED_FEATURE, + "Morph targets are unsupported."); + } + } + } + + // Check for sparse accessors. + for (const auto &accessor : gltf_model_.accessors) { + if (accessor.sparse.isSparse) { + return Status(Status::UNSUPPORTED_FEATURE, + "Sparse accessors are unsupported."); + } + } + + // Check for extensions. + for (const auto &extension : gltf_model_.extensionsRequired) { + if (extension != "KHR_materials_unlit" && + extension != "KHR_texture_transform" && + extension != "KHR_draco_mesh_compression") { + return Status(Status::UNSUPPORTED_FEATURE, + extension + " is unsupported."); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeNode(int node_index, + const Eigen::Matrix4d &parent_matrix) { + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + const std::unique_ptr trsm = GetNodeTrsMatrix(node); + const Eigen::Matrix4d node_matrix = + parent_matrix * trsm->ComputeTransformationMatrix(); + + if (node.mesh >= 0) { + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(DecodePrimitive(primitive, node_matrix)); + } + } + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR(DecodeNode(node.children[i], node_matrix)); + } + return OkStatus(); +} + +StatusOr GltfDecoder::DecodePrimitiveAttributeCount( + const tinygltf::Primitive &primitive) const { + // Use the first primitive attribute as all attributes have the same entry + // count according to glTF 2.0 spec. + if (primitive.attributes.empty()) { + return Status(Status::DRACO_ERROR, "Primitive has no attributes."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[primitive.attributes.begin()->second]; + return accessor.count; +} + +StatusOr GltfDecoder::DecodePrimitiveIndicesCount( + const tinygltf::Primitive &primitive) const { + if (primitive.indices < 0) { + // Primitive has implicit indices [0, 1, 2, 3, ...]. Determine indices count + // based on entry count of a primitive attribute. + return DecodePrimitiveAttributeCount(primitive); + } + const tinygltf::Accessor &indices = gltf_model_.accessors[primitive.indices]; + return indices.count; +} + +StatusOr> GltfDecoder::DecodePrimitiveIndices( + const tinygltf::Primitive &primitive) const { + std::vector indices_data; + if (primitive.indices < 0) { + // Primitive has implicit indices [0, 1, 2, 3, ...]. Create indices based on + // entry count of a primitive attribute. + DRACO_ASSIGN_OR_RETURN(const int num_vertices, + DecodePrimitiveAttributeCount(primitive)); + indices_data.reserve(num_vertices); + for (int i = 0; i < num_vertices; i++) { + indices_data.push_back(i); + } + } else { + // Get indices from the primitive's indices property. + const tinygltf::Accessor &indices = + gltf_model_.accessors[primitive.indices]; + if (indices.count <= 0) { + return Status(Status::DRACO_ERROR, "Could not convert indices."); + } + DRACO_ASSIGN_OR_RETURN(indices_data, + CopyDataAsUint32(gltf_model_, indices)); + } + return indices_data; +} + +Status GltfDecoder::DecodePrimitive(const tinygltf::Primitive &primitive, + const Eigen::Matrix4d &transform_matrix) { + if (primitive.mode != TINYGLTF_MODE_TRIANGLES && + primitive.mode != TINYGLTF_MODE_POINTS) { + return Status(Status::DRACO_ERROR, + "Primitive does not contain triangles or points."); + } + + // Store the transformation scale of this primitive loading as draco::Mesh. + if (scene_ == nullptr) { + // TODO(vytyaz): Do something for non-uniform scaling. + const float scale = transform_matrix.col(0).norm(); + gltf_primitive_material_to_scales_[primitive.material].push_back(scale); + } + + // Handle indices first. + DRACO_ASSIGN_OR_RETURN(const std::vector indices_data, + DecodePrimitiveIndices(primitive)); + const int number_of_faces = indices_data.size() / 3; + const int number_of_points = indices_data.size(); + + for (const auto &attribute : primitive.attributes) { + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + + const int att_id = + attribute_name_to_draco_mesh_attribute_id_[attribute.first]; + if (att_id == -1) { + continue; + } + + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_faces, + transform_matrix, &mb_)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_points, + transform_matrix, &pb_)); + } + } + + // Add the material data only if there is more than one material. + if (gltf_primitive_material_to_draco_material_.size() > 1) { + const int material_index = primitive.material; + const auto it = + gltf_primitive_material_to_draco_material_.find(material_index); + if (it != gltf_primitive_material_to_draco_material_.end()) { + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR( + AddMaterialDataToBuilder(it->second, number_of_faces, &mb_)); + } else { + DRACO_RETURN_IF_ERROR( + AddMaterialDataToBuilder(it->second, number_of_points, &pb_)); + } + } + } + + next_face_id_ += number_of_faces; + next_point_id_ += number_of_points; + return OkStatus(); +} + +Status GltfDecoder::NodeGatherAttributeAndMaterialStats( + const tinygltf::Node &node) { + if (node.mesh >= 0) { + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(AccumulatePrimitiveStats(primitive)); + + const auto it = + gltf_primitive_material_to_draco_material_.find(primitive.material); + if (it == gltf_primitive_material_to_draco_material_.end()) { + gltf_primitive_material_to_draco_material_[primitive.material] = + gltf_primitive_material_to_draco_material_.size(); + } + } + } + for (int i = 0; i < node.children.size(); ++i) { + const tinygltf::Node &child = gltf_model_.nodes[node.children[i]]; + DRACO_RETURN_IF_ERROR(NodeGatherAttributeAndMaterialStats(child)); + } + + return OkStatus(); +} + +Status GltfDecoder::GatherAttributeAndMaterialStats() { + for (const auto &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + const tinygltf::Node &node = gltf_model_.nodes[scene.nodes[i]]; + DRACO_RETURN_IF_ERROR(NodeGatherAttributeAndMaterialStats(node)); + } + } + return OkStatus(); +} + +void GltfDecoder::SumAttributeStats(const std::string &attribute_name, + int count) { + // We know that there must be a valid entry for |attribute_name| at this time. + mesh_attribute_data_[attribute_name].total_attribute_counts += count; +} + +Status GltfDecoder::CheckTypes(const std::string &attribute_name, + int component_type, int type, bool normalized) { + auto it_mad = mesh_attribute_data_.find(attribute_name); + + if (it_mad == mesh_attribute_data_.end()) { + MeshAttributeData mad; + mad.component_type = component_type; + mad.attribute_type = type; + mad.normalized = normalized; + mesh_attribute_data_[attribute_name] = mad; + return OkStatus(); + } + if (it_mad->second.component_type != component_type) { + return Status( + Status::DRACO_ERROR, + attribute_name + " attribute component type does not match previous."); + } + if (it_mad->second.attribute_type != type) { + return Status(Status::DRACO_ERROR, + attribute_name + " attribute type does not match previous."); + } + if (it_mad->second.normalized != normalized) { + return Status( + Status::DRACO_ERROR, + attribute_name + + " attribute normalized property does not match previous."); + } + + return OkStatus(); +} + +Status GltfDecoder::AccumulatePrimitiveStats( + const tinygltf::Primitive &primitive) { + DRACO_ASSIGN_OR_RETURN(const int indices_count, + DecodePrimitiveIndicesCount(primitive)); + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + total_face_indices_count_ += indices_count; + } else if (primitive.mode == TINYGLTF_MODE_POINTS) { + total_point_indices_count_ += indices_count; + } else { + return ErrorStatus("Unsupported primitive indices mode."); + } + + for (const auto &attribute : primitive.attributes) { + if (attribute.second >= gltf_model_.accessors.size()) { + return ErrorStatus("Invalid accessor."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + + DRACO_RETURN_IF_ERROR(CheckTypes(attribute.first, accessor.componentType, + accessor.type, accessor.normalized)); + SumAttributeStats(attribute.first, accessor.count); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddAttributesToDracoMesh(BuilderT *builder) { + for (const auto &attribute : mesh_attribute_data_) { + const GeometryAttribute::Type draco_att_type = + GltfAttributeToDracoAttribute(attribute.first); + if (draco_att_type == GeometryAttribute::INVALID) { + // Map an invalid attribute to attribute id -1 that will be ignored and + // not included in the Draco mesh. + attribute_name_to_draco_mesh_attribute_id_[attribute.first] = -1; + continue; + } + DRACO_ASSIGN_OR_RETURN( + const int att_id, + AddAttribute(draco_att_type, attribute.second.component_type, + attribute.second.attribute_type, builder)); + attribute_name_to_draco_mesh_attribute_id_[attribute.first] = att_id; + } + + // Add the material attribute. + if (gltf_model_.materials.size() > 1) { + draco::DataType component_type = DT_UINT32; + if (gltf_model_.materials.size() < 256) { + component_type = DT_UINT8; + } else if (gltf_model_.materials.size() < (1 << 16)) { + component_type = DT_UINT16; + } + material_att_id_ = + builder->AddAttribute(GeometryAttribute::MATERIAL, 1, component_type); + } + + return OkStatus(); +} + +template +Status GltfDecoder::AddAttributeValuesToBuilder( + const std::string &attribute_name, const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + BuilderT *builder) { + const bool reverse_winding = Determinant(transform_matrix) < 0; + if (attribute_name == "TEXCOORD_0" || attribute_name == "TEXCOORD_1") { + DRACO_RETURN_IF_ERROR(AddTexCoordToBuilder(accessor, indices_data, att_id, + number_of_elements, + reverse_winding, builder)); + } else if (attribute_name == "TANGENT") { + const Eigen::Matrix4d matrix = UpdateMatrixForNormals(transform_matrix); + DRACO_RETURN_IF_ERROR(AddTangentToBuilder(accessor, indices_data, att_id, + number_of_elements, matrix, + reverse_winding, builder)); + } else if (attribute_name == "POSITION" || attribute_name == "NORMAL") { + const Eigen::Matrix4d matrix = + (attribute_name == "NORMAL") ? UpdateMatrixForNormals(transform_matrix) + : transform_matrix; + const bool normalize = (attribute_name == "NORMAL"); + DRACO_RETURN_IF_ERROR(AddTransformedDataToBuilder( + accessor, indices_data, att_id, number_of_elements, matrix, normalize, + reverse_winding, builder)); + } else if (attribute_name.rfind("_FEATURE_ID_") == 0) { + DRACO_RETURN_IF_ERROR(AddFeatureIdToBuilder( + accessor, indices_data, att_id, number_of_elements, reverse_winding, + attribute_name, builder)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, + att_id, number_of_elements, + reverse_winding, builder)); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddTangentToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + for (int v = 0; v < data.size(); ++v) { + Eigen::Vector4d vec4(data[v][0], data[v][1], data[v][2], 1); + vec4 = transform_matrix * vec4; + + // Normalize the data. + Eigen::Vector3d vec3(vec4[0], vec4[1], vec4[2]); + vec3 = vec3.normalized(); + for (int i = 0; i < 3; ++i) { + vec4[i] = vec3[i]; + } + + // Add back the original w component. + vec4[3] = data[v][3]; + for (int i = 0; i < 4; ++i) { + data[v][i] = vec4[i]; + } + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +Status GltfDecoder::AddTexCoordToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + // glTF stores texture coordinates flipped on the horizontal axis compared to + // how Draco stores texture coordinates. + for (auto &uv : data) { + uv[1] = 1.0 - uv[1]; + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +Status GltfDecoder::AddFeatureIdToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, + const std::string &attribute_name, BuilderT *builder) { + // Check that the feature ID attribute has correct type. + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + if (num_components != 1) { + return ErrorStatus("Invalid feature ID attribute type."); + } + const draco::DataType draco_component_type = + GltfComponentTypeToDracoType(accessor.componentType); + if (draco_component_type != DT_UINT8 && draco_component_type != DT_UINT16 && + draco_component_type != DT_FLOAT32) { + return ErrorStatus("Invalid feature ID attribute component type."); + } + + // Set feature ID attribute values to mesh faces. + DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, att_id, + number_of_elements, + reverse_winding, builder)); + + // Store feature ID attribute name with index like _FEATURE_ID_5 in Draco + // attribute metadata. + std::unique_ptr metadata(new draco::AttributeMetadata()); + metadata->AddEntryString("attribute_name", attribute_name); + builder->AddAttributeMetadata(att_id, std::move(metadata)); + return OkStatus(); +} + +template +Status GltfDecoder::AddTransformedDataToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + bool normalize, bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + for (int v = 0; v < data.size(); ++v) { + Eigen::Vector4d vec4(data[v][0], data[v][1], data[v][2], 1); + vec4 = transform_matrix * vec4; + Eigen::Vector3d vec3(vec4[0], vec4[1], vec4[2]); + if (normalize) { + vec3 = vec3.normalized(); + } + for (int i = 0; i < 3; ++i) { + data[v][i] = vec3[i]; + } + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +void GltfDecoder::SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, + bool reverse_winding, + TriangleSoupMeshBuilder *builder) { + SetValuesPerFace(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); +} + +template +void GltfDecoder::SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, + bool reverse_winding, + PointCloudBuilder *builder) { + for (int i = 0; i < number_of_elements; ++i) { + const uint32_t v_id = indices_data[i]; + const PointIndex pi(v_id + next_point_id_); + builder->SetAttributeValueForPoint(att_id, pi, + GetDataContentAddress(data[v_id])); + } +} + +template +void GltfDecoder::SetValuesPerFace(const std::vector &indices_data, + int att_id, int number_of_faces, + const std::vector &data, + bool reverse_winding, + TriangleSoupMeshBuilder *mb) { + for (int f = 0; f < number_of_faces; ++f) { + const int base_corner = f * 3; + const uint32_t v_id = indices_data[base_corner]; + const int next_offset = reverse_winding ? 2 : 1; + const int prev_offset = reverse_winding ? 1 : 2; + const uint32_t v_next_id = indices_data[base_corner + next_offset]; + const uint32_t v_prev_id = indices_data[base_corner + prev_offset]; + + const FaceIndex face_index(f + next_face_id_); + mb->SetAttributeValuesForFace(att_id, face_index, + GetDataContentAddress(data[v_id]), + GetDataContentAddress(data[v_next_id]), + GetDataContentAddress(data[v_prev_id])); + } +} + +// Get the address of data content for arithmetic types |T|. +template +const void *GetDataContentAddressImpl(const T &data, + std::true_type /* is_arithmetic */) { + return &data; +} + +// Get the address of data content for vector types |T|. +template +const void *GetDataContentAddressImpl(const T &data, + std::false_type /* is_arithmetic */) { + return data.data(); +} + +template +const void *GltfDecoder::GetDataContentAddress(const T &data) const { + return GetDataContentAddressImpl(data, std::is_arithmetic()); +} + +template +Status GltfDecoder::AddAttributeDataByTypes( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, BuilderT *builder) { + typedef VectorD Vector2u8i; + typedef VectorD Vector3u8i; + typedef VectorD Vector4u8i; + typedef VectorD Vector2u16i; + typedef VectorD Vector3u16i; + typedef VectorD Vector4u16i; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, number_of_elements, + data, reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, number_of_elements, + data, reverse_winding, builder); + } break; + default: + return ErrorStatus("Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC2: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC3: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC4: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + default: + return Status(Status::DRACO_ERROR, "Add attribute data, unknown type."); + } + return OkStatus(); +} + +template +Status GltfDecoder::CopyTextures(T *owner) { + for (int i = 0; i < gltf_model_.images.size(); ++i) { + const tinygltf::Image &image = gltf_model_.images[i]; + if (image.width == -1 || image.height == -1 || image.component == -1) { + // TinyGLTF does not return an error when it cannot find an image. It will + // add an image with negative values. + return Status(Status::DRACO_ERROR, "Error loading image."); + } + + std::unique_ptr draco_texture(new Texture()); + + // Update mapping between glTF images and textures in the texture library. + gltf_image_to_draco_texture_[i] = draco_texture.get(); + + DRACO_ASSIGN_OR_RETURN(std::unique_ptr source_image, + GetSourceImage(gltf_model_, image, *draco_texture)); + if (source_image->encoded_data().empty() && + !source_image->filename().empty()) { + // Update filename of source image to be relative of the glTF file. + std::string dirname; + std::string basename; + SplitPath(input_file_name_, &dirname, &basename); + source_image->set_filename(dirname + "/" + source_image->filename()); + } + draco_texture->set_source_image(*source_image); + + owner->GetMaterialLibrary().MutableTextureLibrary().PushTexture( + std::move(draco_texture)); + } + return OkStatus(); +} + +void GltfDecoder::SetAttributePropertiesOnDracoMesh(Mesh *mesh) { + for (const auto &mad : mesh_attribute_data_) { + const int att_id = attribute_name_to_draco_mesh_attribute_id_[mad.first]; + if (att_id == -1) { + continue; + } + if (mad.second.normalized) { + mesh->attribute(att_id)->set_normalized(true); + } + } +} + +Status GltfDecoder::AddMaterialsToDracoMesh(Mesh *mesh) { + bool is_normal_map_used = false; + + int default_material_index = -1; + const auto it = gltf_primitive_material_to_draco_material_.find(-1); + if (it != gltf_primitive_material_to_draco_material_.end()) { + default_material_index = it->second; + } + + int output_material_index = 0; + for (int input_material_index = 0; + input_material_index < gltf_model_.materials.size(); + ++input_material_index) { + if (default_material_index == input_material_index) { + // Insert a default material here for primitives that did not have a + // material index. + mesh->GetMaterialLibrary().MutableMaterial(output_material_index++); + } + + Material *const output_material = + mesh->GetMaterialLibrary().MutableMaterial(output_material_index++); + DRACO_RETURN_IF_ERROR( + AddGltfMaterial(input_material_index, output_material)); + if (output_material->GetTextureMapByType( + TextureMap::NORMAL_TANGENT_SPACE)) { + is_normal_map_used = true; + } + } + + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilder(int material_value, + int number_of_elements, + BuilderT *builder) { + if (gltf_primitive_material_to_draco_material_.size() < 256) { + const uint8_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } else if (gltf_primitive_material_to_draco_material_.size() < (1 << 16)) { + const uint16_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } else { + const uint32_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilderInternal( + T material_value, int number_of_faces, TriangleSoupMeshBuilder *builder) { + for (int f = 0; f < number_of_faces; ++f) { + const FaceIndex face_index(f + next_face_id_); + builder->SetPerFaceAttributeValueForFace(material_att_id_, face_index, + &material_value); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilderInternal( + T material_value, int number_of_points, PointCloudBuilder *builder) { + for (int pi = 0; pi < number_of_points; ++pi) { + const PointIndex point_index(pi + next_point_id_); + builder->SetAttributeValueForPoint(material_att_id_, point_index, + &material_value); + } + return OkStatus(); +} + +Status GltfDecoder::CheckAndAddTextureToDracoMaterial( + int texture_index, int tex_coord_attribute_index, + const tinygltf::ExtensionMap &tex_info_ext, Material *material, + TextureMap::Type type) { + if (texture_index < 0) { + return OkStatus(); + } + + const tinygltf::Texture &input_texture = gltf_model_.textures[texture_index]; + int source_index = input_texture.source; + + const auto texture_it = gltf_image_to_draco_texture_.find(source_index); + if (texture_it != gltf_image_to_draco_texture_.end()) { + Texture *const texture = texture_it->second; + // Default GLTF 2.0 sampler uses REPEAT mode along both S and T directions. + TextureMap::WrappingMode wrapping_mode(TextureMap::REPEAT); + TextureMap::FilterType min_filter = TextureMap::UNSPECIFIED; + TextureMap::FilterType mag_filter = TextureMap::UNSPECIFIED; + + if (input_texture.sampler >= 0) { + const tinygltf::Sampler &sampler = + gltf_model_.samplers[input_texture.sampler]; + DRACO_ASSIGN_OR_RETURN(wrapping_mode.s, + TinyGltfToDracoAxisWrappingMode(sampler.wrapS)); + DRACO_ASSIGN_OR_RETURN(wrapping_mode.t, + TinyGltfToDracoAxisWrappingMode(sampler.wrapT)); + DRACO_ASSIGN_OR_RETURN(min_filter, + TinyGltfToDracoFilterType(sampler.minFilter)); + DRACO_ASSIGN_OR_RETURN(mag_filter, + TinyGltfToDracoFilterType(sampler.magFilter)); + } + if (tex_coord_attribute_index < 0 || tex_coord_attribute_index > 1) { + return Status(Status::DRACO_ERROR, "Incompatible tex coord index."); + } + TextureTransform transform; + DRACO_ASSIGN_OR_RETURN(const bool has_transform, + CheckKhrTextureTransform(tex_info_ext, &transform)); + if (has_transform) { + DRACO_RETURN_IF_ERROR(material->SetTextureMap( + texture, type, wrapping_mode, min_filter, mag_filter, transform, + tex_coord_attribute_index)); + } else { + DRACO_RETURN_IF_ERROR( + material->SetTextureMap(texture, type, wrapping_mode, min_filter, + mag_filter, tex_coord_attribute_index)); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeGltfToScene() { + DRACO_RETURN_IF_ERROR(GatherAttributeAndMaterialStats()); + DRACO_RETURN_IF_ERROR(AddLightsToScene()); + DRACO_RETURN_IF_ERROR(AddMaterialsVariantsNamesToScene()); + DRACO_RETURN_IF_ERROR(AddStructuralMetadataToGeometry(scene_.get())); + DRACO_RETURN_IF_ERROR(CopyTextures(scene_.get())); + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + DRACO_RETURN_IF_ERROR( + DecodeNodeForScene(scene.nodes[i], kInvalidSceneNodeIndex)); + scene_->AddRootNodeIndex(gltf_node_to_scenenode_index_[scene.nodes[i]]); + } + } + + DRACO_RETURN_IF_ERROR(AddAnimationsToScene()); + DRACO_RETURN_IF_ERROR(AddMaterialsToScene()); + DRACO_RETURN_IF_ERROR(AddSkinsToScene()); + MoveNonMaterialTextures(scene_.get()); + + return OkStatus(); +} + +Status GltfDecoder::AddLightsToScene() { + // Add all lights to Draco scene. + for (const auto &light : gltf_model_.lights) { + // Add a new light to the scene. + const LightIndex light_index = scene_->AddLight(); + Light *scene_light = scene_->GetLight(light_index); + + // Decode light type. + const std::map types = { + {"directional", Light::DIRECTIONAL}, + {"point", Light::POINT}, + {"spot", Light::SPOT}}; + if (types.count(light.type) == 0) { + return ErrorStatus("Light type is invalid."); + } + scene_light->SetType(types.at(light.type)); + + // Decode spot light properties. + if (scene_light->GetType() == Light::SPOT) { + scene_light->SetInnerConeAngle(light.spot.innerConeAngle); + scene_light->SetOuterConeAngle(light.spot.outerConeAngle); + } + + // Decode other light properties. + scene_light->SetName(light.name); + if (!light.color.empty()) { // Empty means that color is not specified. + if (light.color.size() != 3) { + return ErrorStatus("Light color is malformed."); + } + scene_light->SetColor( + Vector3f(light.color[0], light.color[1], light.color[2])); + } + scene_light->SetIntensity(light.intensity); + if (light.range != 0.0) { // Zero means that range is not specified. + if (light.range < 0.0) { + return ErrorStatus("Light range must be positive."); + } + scene_light->SetRange(light.range); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddMaterialsVariantsNamesToScene() { + // Check whether the scene has materials variants. + const auto &e = gltf_model_.extensions.find("KHR_materials_variants"); + if (e == gltf_model_.extensions.end()) { + // The scene has no materials variants. + return OkStatus(); + } + + // Decode all materials variants names into Draco scene from JSON like this: + // "KHR_materials_variants": { + // "variants": [ + // {"name": "Loki" }, + // {"name": "Odin" }, + // ] + // } + const tinygltf::Value::Object &o = e->second.Get(); + const auto &variants = o.find("variants"); + if (variants == o.end()) { + return ErrorStatus("Materials variants extension with names is malformed."); + } + const tinygltf::Value &variants_array = variants->second; + if (!variants_array.IsArray()) { + return ErrorStatus("Materials variants names array is malformed."); + } + for (int i = 0; i < variants_array.Size(); i++) { + const auto &variant_object = variants_array.Get(i); + if (!variant_object.IsObject() || !variant_object.Has("name")) { + return ErrorStatus("Materials variants name is missing."); + } + const auto &name_string = variant_object.Get("name"); + if (!name_string.IsString()) { + return ErrorStatus("Materials variant name is malformed."); + } + const std::string &name = name_string.Get(); + scene_->GetMaterialLibrary().AddMaterialsVariant(name); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddStructuralMetadataToGeometry(GeometryT *geometry) { + // Check whether the glTF model has structural metadata. + const auto &e = gltf_model_.extensions.find("EXT_structural_metadata"); + if (e == gltf_model_.extensions.end()) { + // The glTF model has no structural metadata. + return OkStatus(); + } + const tinygltf::Value::Object &o = e->second.Get(); + + // Decode property table schema. + { + const auto &value = o.find("schema"); + if (value == o.end()) { + return ErrorStatus("Structural metadata extension has no schema."); + } + const tinygltf::Value &object = value->second; + if (!object.IsObject()) { + return ErrorStatus("Structural metadata extension schema is malformed."); + } + + // Decodes tinygltf::Value into PropertyTable::Schema::Object. + struct SchemaParser { + static Status Parse(const tinygltf::Value &value, + PropertyTable::Schema::Object *object) { + switch (value.Type()) { + case tinygltf::OBJECT_TYPE: { + for (auto &it : value.Get()) { + object->SetObjects().emplace_back(it.first); + DRACO_RETURN_IF_ERROR( + Parse(it.second, &object->SetObjects().back())); + } + } break; + case tinygltf::ARRAY_TYPE: { + for (int i = 0; i < value.ArrayLen(); ++i) { + object->SetArray().emplace_back(); + DRACO_RETURN_IF_ERROR( + Parse(value.Get(i), &object->SetArray().back())); + } + } break; + case tinygltf::STRING_TYPE: + object->SetString(value.Get()); + break; + case tinygltf::INT_TYPE: + object->SetInteger(value.Get()); + break; + case tinygltf::BOOL_TYPE: + object->SetBoolean(value.Get()); + break; + case tinygltf::REAL_TYPE: + case tinygltf::BINARY_TYPE: + case tinygltf::NULL_TYPE: + default: + // Not used in the schema JSON. + return ErrorStatus("Unsupported JSON type in schema."); + } + return OkStatus(); + } + }; + + // Parse property table schema and set it to |geometry|. + PropertyTable::Schema schema; + DRACO_RETURN_IF_ERROR(SchemaParser::Parse(object, &schema.json)); + geometry->GetStructuralMetadata().SetPropertyTableSchema(schema); + } + + // Decode property tables. + { + const auto &tables = o.find("propertyTables"); + if (tables == o.end()) { + return ErrorStatus( + "Structural metadata extension has no property tables."); + } + const tinygltf::Value &tables_array = tables->second; + if (!tables_array.IsArray()) { + return ErrorStatus("Property tables array is malformed."); + } + + // Loop over all property tables. + for (int i = 0; i < tables_array.Size(); i++) { + // Create a property table and populate it below. + std::unique_ptr property_table(new PropertyTable()); + + const auto &object = tables_array.Get(i); + if (!object.IsObject()) { + return ErrorStatus("Property table is malformed."); + } + const auto o = object.Get(); + + // The "class" property is required. + bool success; + std::string str_value; + DRACO_ASSIGN_OR_RETURN(success, DecodeString("class", o, &str_value)); + if (success) { + property_table->SetClass(str_value); + } else { + return ErrorStatus("Property class is malformed."); + } + + // The "count" property is required. + int int_value; + DRACO_ASSIGN_OR_RETURN(success, DecodeInt("count", o, &int_value)); + if (success) { + property_table->SetCount(int_value); + } else { + return ErrorStatus("Property count is malformed."); + } + + // The "name" property is optional. + DRACO_ASSIGN_OR_RETURN(success, DecodeString("name", o, &str_value)); + if (success) { + property_table->SetName(str_value); + } + + // Decode property table properties (columns). + { + constexpr char kName[] = "properties"; + if (!object.Has(kName)) { + return ErrorStatus("Property table is malformed."); + } + const tinygltf::Value &value = object.Get(kName); + if (!value.IsObject()) { + return ErrorStatus( + "Property table properties property is malformed."); + } + + // Loop over property table properties. + for (const auto &key : value.Keys()) { + // Create a property table property and populate it below. + std::unique_ptr property( + new PropertyTable::Property()); + + const auto &property_object = value.Get(key); + if (!property_object.IsObject()) { + return ErrorStatus("Property entry is malformed."); + } + property->SetName(key); + const auto o = property_object.Get(); + + // The "values" property is required. + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("values", o, &property->GetData())); + if (!success) { + return ErrorStatus("Property values property is malformed."); + } + + // All other properties are not required. + DRACO_ASSIGN_OR_RETURN( + success, DecodeString("stringOffsetType", o, &str_value)); + if (success) { + property->GetStringOffsets().type = str_value; + } + DRACO_ASSIGN_OR_RETURN( + success, DecodeString("arrayOffsetType", o, &str_value)); + if (success) { + property->GetArrayOffsets().type = str_value; + } + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("arrayOffsets", o, + &property->GetArrayOffsets().data)); + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("stringOffsets", o, + &property->GetStringOffsets().data)); + + // Add property to the property table. + property_table->AddProperty(std::move(property)); + } + } + + // Add property table to structural metadata. + geometry->GetStructuralMetadata().AddPropertyTable( + std::move(property_table)); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddAnimationsToScene() { + for (const auto &animation : gltf_model_.animations) { + const AnimationIndex animation_index = scene_->AddAnimation(); + Animation *const encoder_animation = scene_->GetAnimation(animation_index); + encoder_animation->SetName(animation.name); + + for (const tinygltf::AnimationChannel &channel : animation.channels) { + const auto it = gltf_node_to_scenenode_index_.find(channel.target_node); + if (it == gltf_node_to_scenenode_index_.end()) { + return Status(Status::DRACO_ERROR, "Could not find Node in the scene."); + } + DRACO_RETURN_IF_ERROR(TinyGltfUtils::AddChannelToAnimation( + gltf_model_, animation, channel, it->second.value(), + encoder_animation)); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeNodeForScene(int node_index, + SceneNodeIndex parent_index) { + SceneNodeIndex scene_node_index = kInvalidSceneNodeIndex; + SceneNode *scene_node = nullptr; + bool is_new_node; + if (gltf_scene_graph_mode_ == GltfSceneGraphMode::DAG && + gltf_node_to_scenenode_index_.find(node_index) != + gltf_node_to_scenenode_index_.end()) { + // Node has been decoded already. + scene_node_index = gltf_node_to_scenenode_index_[node_index]; + scene_node = scene_->GetNode(scene_node_index); + is_new_node = false; + } else { + scene_node_index = scene_->AddNode(); + // Update mapping between glTF Nodes and indices in the scene. + gltf_node_to_scenenode_index_[node_index] = scene_node_index; + + scene_node = scene_->GetNode(scene_node_index); + is_new_node = true; + } + + if (parent_index != kInvalidSceneNodeIndex) { + scene_node->AddParentIndex(parent_index); + SceneNode *const parent_node = scene_->GetNode(parent_index); + parent_node->AddChildIndex(scene_node_index); + } + + if (!is_new_node) { + return OkStatus(); + } + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + if (!node.name.empty()) { + scene_node->SetName(node.name); + } + std::unique_ptr trsm = GetNodeTrsMatrix(node); + scene_node->SetTrsMatrix(*trsm); + if (node.skin >= 0) { + // Save the index to the source skins in the node. This will be updated + // later when the skins are processed. + scene_node->SetSkinIndex(SkinIndex(node.skin)); + } + if (node.mesh >= 0) { + // Check if we have already parsed this glTF Mesh. + const auto it = gltf_mesh_to_scene_mesh_group_.find(node.mesh); + if (it != gltf_mesh_to_scene_mesh_group_.end()) { + // We already processed this glTF mesh. + scene_node->SetMeshGroupIndex(it->second); + } else { + const MeshGroupIndex scene_mesh_group_index = scene_->AddMeshGroup(); + MeshGroup *const scene_mesh = + scene_->GetMeshGroup(scene_mesh_group_index); + + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + if (!mesh.name.empty()) { + scene_mesh->SetName(mesh.name); + } + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(DecodePrimitiveForScene(primitive, scene_mesh)); + } + scene_node->SetMeshGroupIndex(scene_mesh_group_index); + gltf_mesh_to_scene_mesh_group_[node.mesh] = scene_mesh_group_index; + } + } + + // Decode light index. + const auto &e = node.extensions.find("KHR_lights_punctual"); + if (e != node.extensions.end()) { + const tinygltf::Value::Object &o = e->second.Get(); + const auto &light = o.find("light"); + if (light != o.end()) { + const tinygltf::Value &value = light->second; + if (!value.IsInt()) { + return ErrorStatus("Node light index is malformed."); + } + const int light_index = value.Get(); + if (light_index < 0 || light_index >= scene_->NumLights()) { + return ErrorStatus("Node light index is out of bounds."); + } + scene_node->SetLightIndex(LightIndex(light_index)); + } + } + + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR( + DecodeNodeForScene(node.children[i], scene_node_index)); + } + return OkStatus(); +} + +Status GltfDecoder::DecodePrimitiveForScene( + const tinygltf::Primitive &primitive, MeshGroup *mesh_group) { + if (primitive.mode != TINYGLTF_MODE_TRIANGLES && + primitive.mode != TINYGLTF_MODE_POINTS) { + return Status(Status::DRACO_ERROR, + "Primitive does not contain triangles or points."); + } + + // Decode materials variants mappings if present in this primitive. + std::vector mappings; + const auto &e = primitive.extensions.find("KHR_materials_variants"); + if (e != primitive.extensions.end()) { + DRACO_RETURN_IF_ERROR(DecodeMaterialsVariantsMappings( + e->second.Get(), &mappings)); + } + + const PrimitiveSignature signature(primitive); + const auto existing_mesh_index = + gltf_primitive_to_draco_mesh_index_.find(signature); + if (existing_mesh_index != gltf_primitive_to_draco_mesh_index_.end()) { + mesh_group->AddMeshInstance( + {existing_mesh_index->second, primitive.material, mappings}); + return OkStatus(); + } + + // Handle indices first. + DRACO_ASSIGN_OR_RETURN(const std::vector indices_data, + DecodePrimitiveIndices(primitive)); + const int number_of_faces = indices_data.size() / 3; + const int number_of_points = indices_data.size(); + + // Note that glTF mesh |primitive| has no name; no name is set to Draco mesh. + TriangleSoupMeshBuilder mb; + PointCloudBuilder pb; + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + mb.Start(number_of_faces); + } else { + pb.Start(number_of_points); + } + + std::set normalized_attributes; + for (const auto &attribute : primitive.attributes) { + if (attribute.second >= gltf_model_.accessors.size()) { + return ErrorStatus("Invalid accessor."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + const int component_type = accessor.componentType; + const int type = accessor.type; + const bool normalized = accessor.normalized; + int att_id = -1; + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_ASSIGN_OR_RETURN( + att_id, AddAttribute(attribute.first, component_type, type, &mb)); + } else { + DRACO_ASSIGN_OR_RETURN( + att_id, AddAttribute(attribute.first, component_type, type, &pb)); + } + if (att_id == -1) { + continue; + } + if (normalized) { + normalized_attributes.insert(att_id); + } + + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_faces, + Eigen::Matrix4d::Identity(), &mb)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_points, + Eigen::Matrix4d::Identity(), &pb)); + } + } + + int material_index = primitive.material; + + DRACO_ASSIGN_OR_RETURN( + std::unique_ptr mesh, + BuildMeshFromBuilder(primitive.mode == TINYGLTF_MODE_TRIANGLES, &mb, + &pb)); + + // Set all normalized flags for appropriate attributes. + for (const int32_t att_id : normalized_attributes) { + mesh->attribute(att_id)->set_normalized(true); + } + // Decode mesh feature ID sets if present in this primitive. + DRACO_RETURN_IF_ERROR(DecodeMeshFeatures( + primitive, &scene_->GetMaterialLibrary().MutableTextureLibrary(), + mesh.get())); + + const MeshIndex mesh_index = scene_->AddMesh(std::move(mesh)); + if (mesh_index == kInvalidMeshIndex) { + return Status(Status::DRACO_ERROR, "Could not add Draco mesh to scene."); + } + mesh_group->AddMeshInstance({mesh_index, material_index, mappings}); + + gltf_primitive_to_draco_mesh_index_[signature] = mesh_index; + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialsVariantsMappings( + const tinygltf::Value::Object &extension, + std::vector *mappings) { + // Decode all materials variants mappings from JSON like this: + // "KHR_materials_variants" : { + // "mappings": [ + // { + // "material": 2, + // "variants": [0, 2, 4] + // }, + // { + // "material": 3, + // "variants": [1, 3] + // } + // ] + // } + const auto &mappings_object = extension.find("mappings"); + if (mappings_object == extension.end()) { + return ErrorStatus("Materials variants extension is malformed."); + } + const tinygltf::Value &mappings_array = mappings_object->second; + if (!mappings_array.IsArray()) { + return ErrorStatus("Materials variants mappings array is malformed."); + } + for (int i = 0; i < mappings_array.Size(); i++) { + const auto &mapping_object = mappings_array.Get(i); + if (!mapping_object.IsObject() || !mapping_object.Has("material") || + !mapping_object.Has("variants")) { + return ErrorStatus("Materials variants mapping is malformed."); + } + const tinygltf::Value &material_int = mapping_object.Get("material"); + if (!material_int.IsInt()) { + return ErrorStatus("Materials variant mapping material is malformed."); + } + const int material = material_int.Get(); + const tinygltf::Value &variants_array = mapping_object.Get("variants"); + if (!variants_array.IsArray()) { + return ErrorStatus("Materials variant mapping variants is malformed."); + } + std::vector variants; + for (int j = 0; j < variants_array.Size(); j++) { + const tinygltf::Value &variant_int = variants_array.Get(j); + if (!variant_int.IsInt()) { + return ErrorStatus("Materials variants mapping variant is malformed."); + } + variants.push_back(variant_int.Get()); + } + mappings->push_back({material, variants}); + } + return OkStatus(); +} + +Status GltfDecoder::DecodeMeshFeatures(const tinygltf::Primitive &primitive, + TextureLibrary *texture_library, + Mesh *mesh) { + const auto &e = primitive.extensions.find("EXT_mesh_features"); + if (e == primitive.extensions.end()) { + return OkStatus(); + } + std::vector> mesh_features; + DRACO_RETURN_IF_ERROR( + DecodeMeshFeatures(e->second.Get(), + texture_library, &mesh_features)); + for (int i = 0; i < mesh_features.size(); i++) { + const MeshFeaturesIndex mfi = + mesh->AddMeshFeatures(std::move(mesh_features[i])); + if (scene_ == nullptr) { + // If we are decoding to a mesh, we need to restrict the mesh features to + // the primitive's material. + // TODO(ostava): This will not work properly when two primitives share the + // same material but have different mesh features. We will need to + // duplicate the materials in this case. + const auto mat_it = + gltf_primitive_material_to_draco_material_.find(primitive.material); + if (mat_it != gltf_primitive_material_to_draco_material_.end()) { + mesh->AddMeshFeaturesMaterialMask(mfi, mat_it->second); + } + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeMeshFeatures( + const tinygltf::Value::Object &extension, TextureLibrary *texture_library, + std::vector> *mesh_features) { + // Decode all mesh feature ID sets from JSON like this: + // "EXT_mesh_features": { + // "featureIds": [ + // { + // "label": "water", + // "featureCount": 2, + // "propertyTable": 0, + // "attribute": 0 + // }, + // { + // "featureCount": 12, + // "nullFeatureId": 100, + // "texture" : { + // "index": 0, + // "texCoord": 0, + // "channels": [0, 1, 2, 3] + // } + // } + // ] + // } + const auto &object = extension.find("featureIds"); + if (object == extension.end()) { + return ErrorStatus("Mesh features extension is malformed."); + } + const tinygltf::Value &array = object->second; + if (!array.IsArray()) { + return ErrorStatus("Mesh features array is malformed."); + } + for (int i = 0; i < array.Size(); i++) { + // Create a new feature ID set object and populate it below. + mesh_features->push_back(std::unique_ptr(new MeshFeatures())); + MeshFeatures &features = *mesh_features->back(); + + const auto &object = array.Get(i); + if (!object.IsObject()) { + return ErrorStatus("Mesh features array entry is malformed."); + } + + // The "featureCount" property is required. + { + constexpr char kName[] = "featureCount"; + if (!object.Has(kName)) { + return ErrorStatus("Mesh features is malformed."); + } + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Feature count property is malformed."); + } + features.SetFeatureCount(value.Get()); + } + + // All other properties are optional. + { + constexpr char kName[] = "nullFeatureId"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Null feature ID property is malformed."); + } + features.SetNullFeatureId(value.Get()); + } + } + { + constexpr char kName[] = "label"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsString()) { + return ErrorStatus("Label property is malformed."); + } + features.SetLabel(value.Get()); + } + } + { + constexpr char kName[] = "attribute"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Attribute property is malformed."); + } + features.SetAttributeIndex(value.Get()); + } + } + { + constexpr char kName[] = "texture"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsObject()) { + return ErrorStatus("Texture property is malformed."); + } + + // Decode texture contining mesh feature IDs into the |features| object + // via a temporary |material| object. + Material material(texture_library); + const auto &container_object = object.Get(); + DRACO_RETURN_IF_ERROR(DecodeTexture(kName, TextureMap::GENERIC, + container_object, &material)); + features.SetTextureMap( + *material.GetTextureMapByType(TextureMap::GENERIC)); + + // Decode array of texture channel indices. + std::vector channels; + { + constexpr char kName[] = "channels"; + if (value.Has(kName)) { + const tinygltf::Value &array = value.Get(kName); + if (!array.IsArray()) { + return ErrorStatus("Channels property is malformed."); + } + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "Channels value is malformed."); + } + channels.push_back(value.Get()); + } + } else { + channels = {0}; + } + } + features.SetTextureChannels(channels); + } + } + { + constexpr char kName[] = "propertyTable"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Property table property is malformed."); + } + features.SetPropertyTableIndex(value.Get()); + } + } + } + return OkStatus(); +} + +template +StatusOr GltfDecoder::AddAttribute(const std::string &attribute_name, + int component_type, int type, + BuilderT *builder) { + const GeometryAttribute::Type draco_att_type = + GltfAttributeToDracoAttribute(attribute_name); + if (draco_att_type == GeometryAttribute::INVALID) { + // Return attribute id -1 that will be ignored and not included in the mesh. + return -1; + } + DRACO_ASSIGN_OR_RETURN( + const int att_id, + AddAttribute(draco_att_type, component_type, type, builder)); + return att_id; +} + +template +StatusOr GltfDecoder::AddAttribute(GeometryAttribute::Type attribute_type, + int component_type, int type, + BuilderT *builder) { + const int num_components = TinyGltfUtils::GetNumComponentsForType(type); + if (num_components == 0) { + return Status(Status::DRACO_ERROR, + "Could not add attribute with 0 components."); + } + + const draco::DataType draco_component_type = + GltfComponentTypeToDracoType(component_type); + if (draco_component_type == DT_INVALID) { + return Status(Status::DRACO_ERROR, + "Could not add attribute with invalid type."); + } + const int att_id = builder->AddAttribute(attribute_type, num_components, + draco_component_type); + if (att_id < 0) { + return Status(Status::DRACO_ERROR, "Could not add attribute."); + } + return att_id; +} + +StatusOr GltfDecoder::CheckKhrTextureTransform( + const tinygltf::ExtensionMap &extension, TextureTransform *transform) { + bool transform_set = false; + + const auto &e = extension.find("KHR_texture_transform"); + if (e == extension.end()) { + return false; + } + const tinygltf::Value::Object &o = e->second.Get(); + const auto &scale = o.find("scale"); + if (scale != o.end()) { + const tinygltf::Value &array = scale->second; + if (!array.IsArray() || array.Size() != 2) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform scale is malformed."); + } + std::array scale; + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform scale is malformed."); + } + scale[i] = value.Get(); + transform_set = true; + } + transform->set_scale(scale); + } + const auto &rotation = o.find("rotation"); + if (rotation != o.end()) { + const tinygltf::Value &value = rotation->second; + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform rotation is malformed."); + } + transform->set_rotation(value.Get()); + transform_set = true; + } + const auto &offset = o.find("offset"); + if (offset != o.end()) { + const tinygltf::Value &array = offset->second; + if (!array.IsArray() || array.Size() != 2) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform offset is malformed."); + } + std::array offset; + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform offset is malformed."); + } + offset[i] = value.Get(); + transform_set = true; + } + transform->set_offset(offset); + } + const auto &tex_coord = o.find("texCoord"); + if (tex_coord != o.end()) { + const tinygltf::Value &value = tex_coord->second; + if (!value.IsInt()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform texCoord is malformed."); + } + transform->set_tex_coord(value.Get()); + transform_set = true; + } + return transform_set; +} + +Status GltfDecoder::AddGltfMaterial(int input_material_index, + Material *output_material) { + const tinygltf::Material &input_material = + gltf_model_.materials[input_material_index]; + + output_material->SetName(input_material.name); + output_material->SetTransparencyMode( + TinyGltfUtils::TextToMaterialMode(input_material.alphaMode)); + output_material->SetAlphaCutoff(input_material.alphaCutoff); + if (input_material.emissiveFactor.size() == 3) { + output_material->SetEmissiveFactor(Vector3f( + input_material.emissiveFactor[0], input_material.emissiveFactor[1], + input_material.emissiveFactor[2])); + } + const tinygltf::PbrMetallicRoughness &pbr = + input_material.pbrMetallicRoughness; + + if (pbr.baseColorFactor.size() == 4) { + output_material->SetColorFactor( + Vector4f(pbr.baseColorFactor[0], pbr.baseColorFactor[1], + pbr.baseColorFactor[2], pbr.baseColorFactor[3])); + } + output_material->SetMetallicFactor(pbr.metallicFactor); + output_material->SetRoughnessFactor(pbr.roughnessFactor); + output_material->SetDoubleSided(input_material.doubleSided); + + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + pbr.baseColorTexture.index, pbr.baseColorTexture.texCoord, + pbr.baseColorTexture.extensions, output_material, TextureMap::COLOR)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + pbr.metallicRoughnessTexture.index, pbr.metallicRoughnessTexture.texCoord, + pbr.metallicRoughnessTexture.extensions, output_material, + TextureMap::METALLIC_ROUGHNESS)); + + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.normalTexture.index, input_material.normalTexture.texCoord, + input_material.normalTexture.extensions, output_material, + TextureMap::NORMAL_TANGENT_SPACE)); + if (input_material.normalTexture.scale != 1.0) { + output_material->SetNormalTextureScale(input_material.normalTexture.scale); + } + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.occlusionTexture.index, + input_material.occlusionTexture.texCoord, + input_material.occlusionTexture.extensions, output_material, + TextureMap::AMBIENT_OCCLUSION)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.emissiveTexture.index, + input_material.emissiveTexture.texCoord, + input_material.emissiveTexture.extensions, output_material, + TextureMap::EMISSIVE)); + + // Decode material extensions. + DecodeMaterialUnlitExtension(input_material, output_material); + DRACO_RETURN_IF_ERROR( + DecodeMaterialSheenExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialTransmissionExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialClearcoatExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR(DecodeMaterialVolumeExtension( + input_material, input_material_index, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialIorExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialSpecularExtension(input_material, output_material)); + + return OkStatus(); +} + +void GltfDecoder::DecodeMaterialUnlitExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_unlit"); + if (extension_it == input_material.extensions.end()) { + return; + } + + // Set the unlit property in Draco material. + output_material->SetUnlit(true); +} + +Status GltfDecoder::DecodeMaterialSheenExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_sheen"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasSheen(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode sheen color factor. + Vector3f vector; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeVector3f("sheenColorFactor", extension_object, &vector)); + if (success) { + output_material->SetSheenColorFactor(vector); + } + + // Decode sheen roughness factor. + float value; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("sheenRoughnessFactor", extension_object, &value)); + if (success) { + output_material->SetSheenRoughnessFactor(value); + } + + // Decode sheen color texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("sheenColorTexture", + TextureMap::SHEEN_COLOR, extension_object, + output_material)); + + // Decode sheen roughness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("sheenRoughnessTexture", + TextureMap::SHEEN_ROUGHNESS, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialTransmissionExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_transmission"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasTransmission(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode transmission factor. + float value; + DRACO_ASSIGN_OR_RETURN( + const bool success, + DecodeFloat("transmissionFactor", extension_object, &value)); + if (success) { + output_material->SetTransmissionFactor(value); + } + + // Decode transmission texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("transmissionTexture", + TextureMap::TRANSMISSION, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialClearcoatExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_clearcoat"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasClearcoat(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode clearcoat factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("clearcoatFactor", extension_object, &value)); + if (success) { + output_material->SetClearcoatFactor(value); + } + + // Decode clearcoat roughness factor. + DRACO_ASSIGN_OR_RETURN(success, DecodeFloat("clearcoatRoughnessFactor", + extension_object, &value)); + if (success) { + output_material->SetClearcoatRoughnessFactor(value); + } + + // Decode clearcoat texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatTexture", TextureMap::CLEARCOAT, + extension_object, output_material)); + + // Decode clearcoat roughness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatRoughnessTexture", + TextureMap::CLEARCOAT_ROUGHNESS, + extension_object, output_material)); + + // Decode clearcoat normal texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatNormalTexture", + TextureMap::CLEARCOAT_NORMAL, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialVolumeExtension( + const tinygltf::Material &input_material, int input_material_index, + Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_volume"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasVolume(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode thickness factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("thicknessFactor", extension_object, &value)); + if (success) { + // Volume thickness factor is given in the coordinate space of the model. + // When the model is loaded as draco::Mesh, the scene graph transformations + // are applied to position attribute. Since this effectively scales the + // model coordinate space, the volume thickness factor also must be scaled. + // No scaling is done when the model is loaded as draco::Scene. + float scale = 1.0f; + if (scene_ == nullptr) { + if (gltf_primitive_material_to_scales_.count(input_material_index) == 1) { + const std::vector &scales = + gltf_primitive_material_to_scales_[input_material_index]; + + // It is only possible to scale the volume thickness factor if all + // primitives using this material have the same transformation scale. + // An alternative would be to create a separate meterial for each scale. + scale = scales[0]; + for (int i = 1; i < scales.size(); i++) { + // Note that close-enough scales could also be permitted. + if (scales[i] != scale) { + return ErrorStatus("Cannot represent volume thickness in a mesh."); + } + } + } + } + output_material->SetThicknessFactor(scale * value); + } + + // Decode attenuation distance. + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("attenuationDistance", extension_object, &value)); + if (success) { + output_material->SetAttenuationDistance(value); + } + + // Decode attenuation color. + Vector3f vector; + DRACO_ASSIGN_OR_RETURN( + success, DecodeVector3f("attenuationColor", extension_object, &vector)); + if (success) { + output_material->SetAttenuationColor(vector); + } + + // Decode thickness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("thicknessTexture", TextureMap::THICKNESS, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialIorExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_ior"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasIor(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode index of refraction. + float value; + DRACO_ASSIGN_OR_RETURN(const bool success, + DecodeFloat("ior", extension_object, &value)); + if (success) { + output_material->SetIor(value); + } + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialSpecularExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_specular"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasSpecular(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode specular factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("specularFactor", extension_object, &value)); + if (success) { + output_material->SetSpecularFactor(value); + } + + // Decode specular color factor. + Vector3f vector; + DRACO_ASSIGN_OR_RETURN(success, DecodeVector3f("specularColorFactor", + extension_object, &vector)); + if (success) { + output_material->SetSpecularColorFactor(vector); + } + + // Decode speclar texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("specularTexture", TextureMap::SPECULAR, + extension_object, output_material)); + + // Decode specular color texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("specularColorTexture", + TextureMap::SPECULAR_COLOR, + extension_object, output_material)); + + return OkStatus(); +} + +StatusOr GltfDecoder::DecodeFloat(const std::string &name, + const tinygltf::Value::Object &object, + float *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &number = it->second; + if (!number.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + *value = number.Get(); + return true; +} + +StatusOr GltfDecoder::DecodeInt(const std::string &name, + const tinygltf::Value::Object &object, + int *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &number = it->second; + if (!number.IsNumber()) { + return ErrorStatus("Invalid " + name + "."); + } + *value = number.Get(); + return true; +} + +StatusOr GltfDecoder::DecodeString(const std::string &name, + const tinygltf::Value::Object &object, + std::string *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &string = it->second; + if (!string.IsString()) { + return ErrorStatus("Invalid " + name + "."); + } + *value = string.Get(); + return true; +} + +StatusOr GltfDecoder::DecodePropertyTableData( + const std::string &name, const tinygltf::Value::Object &object, + PropertyTable::Property::Data *data) { + int buffer_view_index; + DRACO_ASSIGN_OR_RETURN(const bool success, + DecodeInt(name, object, &buffer_view_index)); + if (!success) { + return false; + } + DRACO_RETURN_IF_ERROR( + CopyDataFromBufferView(gltf_model_, buffer_view_index, &data->data)); + data->target = gltf_model_.bufferViews[buffer_view_index].target; + return true; +} + +StatusOr GltfDecoder::DecodeVector3f( + const std::string &name, const tinygltf::Value::Object &object, + Vector3f *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &array = it->second; + if (!array.IsArray() || array.Size() != 3) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &array_entry = array.Get(i); + if (!array_entry.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + (*value)[i] = array_entry.Get(); + } + return true; +} + +Status GltfDecoder::DecodeTexture(const std::string &name, + TextureMap::Type type, + const tinygltf::Value::Object &object, + Material *material) { + tinygltf::TextureInfo info; + DRACO_RETURN_IF_ERROR(ParseTextureInfo(name, object, &info)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + info.index, info.texCoord, info.extensions, material, type)); + return OkStatus(); +} + +Status GltfDecoder::ParseTextureInfo( + const std::string &texture_name, + const tinygltf::Value::Object &container_object, + tinygltf::TextureInfo *texture_info) { + // Note that tinygltf only parses material textures and not material extension + // textures. This method mimics the behavior of tinygltf's private function + // ParseTextureInfo() in order for Draco to decode extension textures. + + // Do nothing if texture with such name is absent. + const auto &texture_object_it = container_object.find(texture_name); + if (texture_object_it == container_object.end()) { + return OkStatus(); + } + + const tinygltf::Value::Object &texture_object = + texture_object_it->second.Get(); + + // Decode texture index. + const auto &index_it = texture_object.find("index"); + if (index_it != texture_object.end()) { + const tinygltf::Value &value = index_it->second; + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid texture index."); + } + texture_info->index = value.Get(); + } + + // Decode texture coordinate index. + const auto &tex_coord_it = texture_object.find("texCoord"); + if (tex_coord_it != texture_object.end()) { + const tinygltf::Value &value = tex_coord_it->second; + if (!value.IsInt()) { + return Status(Status::DRACO_ERROR, "Invalid texture texCoord."); + } + texture_info->texCoord = value.Get(); + } + + // Decode texture extensions. + const auto &extensions_it = texture_object.find("extensions"); + if (extensions_it != texture_object.end()) { + const tinygltf::Value &extensions = extensions_it->second; + if (!extensions.IsObject()) { + return Status(Status::DRACO_ERROR, "Invalid extension."); + } + for (const std::string &key : extensions.Keys()) { + texture_info->extensions[key] = extensions.Get(key); + } + } + + // Decode texture extras. + const auto &extras_it = texture_object.find("extras"); + if (extras_it != texture_object.end()) { + texture_info->extras = extras_it->second; + } + + return OkStatus(); +} + +Status GltfDecoder::AddMaterialsToScene() { + for (int input_material_index = 0; + input_material_index < gltf_model_.materials.size(); + ++input_material_index) { + Material *const output_material = + scene_->GetMaterialLibrary().MutableMaterial(input_material_index); + DRACO_RETURN_IF_ERROR( + AddGltfMaterial(input_material_index, output_material)); + } + + // Check if we need to add a default material for primitives without an + // assigned material. + const int default_material_index = + scene_->GetMaterialLibrary().NumMaterials(); + bool default_material_needed = false; + for (MeshGroupIndex mgi(0); mgi < scene_->NumMeshGroups(); ++mgi) { + MeshGroup *const mg = scene_->GetMeshGroup(mgi); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + MeshGroup::MeshInstance &mesh_instance = mg->GetMeshInstance(mi); + if (mesh_instance.material_index == -1) { + mesh_instance.material_index = default_material_index; + default_material_needed = true; + } + } + } + if (default_material_needed) { + // Create an empty default material (our defaults correspond to glTF + // defaults). + scene_->GetMaterialLibrary().MutableMaterial(default_material_index); + } + + std::unordered_set meshes_that_need_tangents; + // Check if we need to generate tangent space for any of the loaded meshes. + for (MeshGroupIndex mgi(0); mgi < scene_->NumMeshGroups(); ++mgi) { + const MeshGroup *const mg = scene_->GetMeshGroup(mgi); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + const MeshGroup::MeshInstance &mesh_instance = mg->GetMeshInstance(mi); + const auto tangent_map = + scene_->GetMaterialLibrary() + .GetMaterial(mesh_instance.material_index) + ->GetTextureMapByType(TextureMap::NORMAL_TANGENT_SPACE); + if (tangent_map != nullptr) { + Mesh &mesh = scene_->GetMesh(mesh_instance.mesh_index); + if (mesh.GetNamedAttribute(GeometryAttribute::TANGENT) == nullptr) { + meshes_that_need_tangents.insert(&mesh); + } + } + } + } + + return OkStatus(); +} + +Status GltfDecoder::AddSkinsToScene() { + for (int source_skin_index = 0; source_skin_index < gltf_model_.skins.size(); + ++source_skin_index) { + const tinygltf::Skin &skin = gltf_model_.skins[source_skin_index]; + const SkinIndex skin_index = scene_->AddSkin(); + Skin *const new_skin = scene_->GetSkin(skin_index); + + // The skin index was set previously while processing the nodes. + if (skin_index.value() != source_skin_index) { + return Status(Status::DRACO_ERROR, "Skin indices are mismatched."); + } + + if (skin.inverseBindMatrices >= 0) { + const tinygltf::Accessor &accessor = + gltf_model_.accessors[skin.inverseBindMatrices]; + DRACO_RETURN_IF_ERROR(TinyGltfUtils::AddAccessorToAnimationData( + gltf_model_, accessor, &new_skin->GetInverseBindMatrices())); + } + + if (skin.skeleton >= 0) { + const auto it = gltf_node_to_scenenode_index_.find(skin.skeleton); + if (it == gltf_node_to_scenenode_index_.end()) { + // TODO(b/200317162): If skeleton is not found set the default. + return Status(Status::DRACO_ERROR, + "Could not find skeleton in the skin."); + } + new_skin->SetJointRoot(it->second); + } + + for (int joint : skin.joints) { + const auto it = gltf_node_to_scenenode_index_.find(joint); + if (it == gltf_node_to_scenenode_index_.end()) { + // TODO(b/200317162): If skeleton is not found set the default. + return Status(Status::DRACO_ERROR, + "Could not find skeleton in the skin."); + } + new_skin->AddJoint(it->second); + } + } + return OkStatus(); +} + +void GltfDecoder::MoveNonMaterialTextures(Mesh *mesh) { + std::unordered_set non_material_textures; + for (MeshFeaturesIndex i(0); i < mesh->NumMeshFeatures(); i++) { + Texture *const texture = mesh->GetMeshFeatures(i).GetTextureMap().texture(); + if (texture != nullptr) { + non_material_textures.insert(texture); + } + } + MoveNonMaterialTextures(non_material_textures, + &mesh->GetMaterialLibrary().MutableTextureLibrary(), + &mesh->GetNonMaterialTextureLibrary()); +} + +void GltfDecoder::MoveNonMaterialTextures(Scene *scene) { + std::unordered_set non_material_textures; + for (MeshIndex i(0); i < scene->NumMeshes(); i++) { + for (MeshFeaturesIndex j(0); j < scene->GetMesh(i).NumMeshFeatures(); j++) { + Texture *const texture = + scene->GetMesh(i).GetMeshFeatures(j).GetTextureMap().texture(); + if (texture != nullptr) { + non_material_textures.insert(texture); + } + } + } + MoveNonMaterialTextures(non_material_textures, + &scene->GetMaterialLibrary().MutableTextureLibrary(), + &scene->GetNonMaterialTextureLibrary()); +} + +void GltfDecoder::MoveNonMaterialTextures( + const std::unordered_set &non_material_textures, + TextureLibrary *material_tl, TextureLibrary *non_material_tl) { + // TODO(vytyaz): Consider textures that are both material and non-material. + for (int i = 0; i < material_tl->NumTextures(); i++) { + // Move non-material texture from material to non-material texture library. + if (non_material_textures.count(material_tl->GetTexture(i)) == 1) { + non_material_tl->PushTexture(material_tl->RemoveTexture(i--)); + } + } +} + +bool GltfDecoder::PrimitiveSignature::operator==( + const PrimitiveSignature &signature) const { + return primitive.indices == signature.primitive.indices && + primitive.attributes == signature.primitive.attributes && + primitive.extras == signature.primitive.extras && + primitive.extensions == signature.primitive.extensions && + primitive.mode == signature.primitive.mode && + primitive.targets == signature.primitive.targets; +} + +size_t GltfDecoder::PrimitiveSignature::Hash::operator()( + const PrimitiveSignature &signature) const { + size_t hash = 79; // Magic number. + hash = HashCombine(signature.primitive.attributes.size(), hash); + for (auto it = signature.primitive.attributes.begin(); + it != signature.primitive.attributes.end(); ++it) { + hash = HashCombine(it->first, hash); + hash = HashCombine(it->second, hash); + } + hash = HashCombine(signature.primitive.indices, hash); + hash = HashCombine(signature.primitive.mode, hash); + return hash; +} + +StatusOr> GltfDecoder::BuildMeshFromBuilder( + bool use_mesh_builder, TriangleSoupMeshBuilder *mb, PointCloudBuilder *pb) { + std::unique_ptr mesh; + if (use_mesh_builder) { + mesh = mb->Finalize(); + } else { + std::unique_ptr pc = pb->Finalize(true); + if (pc) { + mesh.reset(new Mesh()); + PointCloud *mesh_pc = mesh.get(); + mesh_pc->Copy(*pc); + } + } + if (!mesh) { + return ErrorStatus("Failed to build Draco mesh from glTF data."); + } + return mesh; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.h new file mode 100644 index 000000000..2ae12106e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder.h @@ -0,0 +1,524 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_DECODER_H_ +#define DRACO_IO_GLTF_DECODER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/decoder_buffer.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/tiny_gltf_utils.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/point_cloud/point_cloud_builder.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Decodes a glTF file and returns a draco::Mesh. All of the |mesh|'s attributes +// will be merged into one draco::Mesh +class GltfDecoder { + public: + GltfDecoder(); + + // Decodes a glTF file stored in the input |file_name| or |buffer| to a Mesh. + // The second form returns a vector of files used as input to the mesh during + // the decoding process. Returns nullptr when decode fails. + StatusOr> DecodeFromFile(const std::string &file_name); + StatusOr> DecodeFromFile( + const std::string &file_name, std::vector *mesh_files); + StatusOr> DecodeFromBuffer(DecoderBuffer *buffer); + + // Decodes a glTF file stored in the input |file_name| or |buffer| to a Scene. + // The second form returns a vector of files used as input to the scene during + // the decoding process. Returns nullptr if the decode fails. + StatusOr> DecodeFromFileToScene( + const std::string &file_name); + StatusOr> DecodeFromFileToScene( + const std::string &file_name, std::vector *scene_files); + StatusOr> DecodeFromBufferToScene( + DecoderBuffer *buffer); + + // Scene graph can be loaded either as a tree or a general directed acyclic + // graph (DAG) that allows multiple parent nodes. By default. we decode the + // scene graph as a tree. If the tree mode is selected and the input contains + // nodes with multiple parents, these nodes are duplicated to form a tree. + // TODO(ostava): Add support for DAG mode to other parts of the Draco + // library. + enum class GltfSceneGraphMode { TREE, DAG }; + void SetSceneGraphMode(GltfSceneGraphMode mode) { + gltf_scene_graph_mode_ = mode; + } + + private: + // Loads |file_name| into |gltf_model_|. Fills |input_files| with paths to all + // input files when non-null. + Status LoadFile(const std::string &file_name, + std::vector *input_files); + + // Loads |gltf_model_| from |buffer| in GLB format. + Status LoadBuffer(const DecoderBuffer &buffer); + + // Builds mesh from |gltf_model_|. + StatusOr> BuildMesh(); + + // Checks |gltf_model_| for unsupported features. If |gltf_model_| contains + // unsupported features then the function will return with a status code of + // UNSUPPORTED_FEATURE. + Status CheckUnsupportedFeatures(); + + // Decodes a glTF Node as well as any child Nodes. If |node| contains a mesh + // it will process all of the mesh's primitives. + Status DecodeNode(int node_index, const Eigen::Matrix4d &parent_matrix); + + // Decodes the number of entries in the first attribute of a given glTF + // |primitive|. Note that all attributes have the same entry count according + // to glTF 2.0 spec. + StatusOr DecodePrimitiveAttributeCount( + const tinygltf::Primitive &primitive) const; + + // Decodes the number of indices in a given glTF |primitive|. If primitive's + // indices property is not defined, the index count is implied from the entry + // count of a primitive attribute. + StatusOr DecodePrimitiveIndicesCount( + const tinygltf::Primitive &primitive) const; + + // Decodes indices property of a given glTF |primitive|. If primitive's + // indices property is not defined, the indices are generated based on entry + // count of a primitive attribute. + StatusOr> DecodePrimitiveIndices( + const tinygltf::Primitive &primitive) const; + + // Decodes a glTF Primitive. All of the |primitive|'s attributes will be + // merged into the draco::Mesh output if they are of the same type that + // already has been decoded. + Status DecodePrimitive(const tinygltf::Primitive &primitive, + const Eigen::Matrix4d &transform_matrix); + + // Sums the number of elements per attribute for |node|'s mesh and any of + // |node|'s children. Fills out the material index map. + Status NodeGatherAttributeAndMaterialStats(const tinygltf::Node &node); + + // Sums the number of elements per attribute for all of the meshes and + // primitives. + Status GatherAttributeAndMaterialStats(); + + // Sums the attribute counts into total_attribute_counts_. + void SumAttributeStats(const std::string &attribute_name, int count); + + // Checks that all the same glTF attribute types in different meshes and + // primitives contain the same characteristics. + Status CheckTypes(const std::string &attribute_name, int component_type, + int type, bool normalized); + + // Accumulates the number of elements per attribute for |primitive|. + Status AccumulatePrimitiveStats(const tinygltf::Primitive &primitive); + + // Adds all of the attributes from the glTF file to a Draco mesh. + // GatherAttributeAndMaterialStats() must be called before this function. The + // GeometryAttribute::MATERIAL attribute will be created only if the glTF file + // contains more than one material. + template + Status AddAttributesToDracoMesh(BuilderT *builder); + + // Copies attribute data from |accessor| and adds it to a Draco mesh using the + // geometry builder |builder|. + template + Status AddAttributeValuesToBuilder(const std::string &attribute_name, + const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + BuilderT *builder); + + // Copies the tangent attribute data from |accessor| and adds it to a Draco + // mesh. This function will transform all of the data by |transform_matrix| + // and then normalize before adding the data to the Draco mesh. + // |indices_data| is the indices data from the glTF file. |att_id| is the + // attribute id of the tangent attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddTangentToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + bool reverse_winding, BuilderT *builder); + + // Copies the texture coordinate attribute data from |accessor| and adds it to + // a Draco mesh. This function will flip the data on the horizontal axis as + // Draco meshes store the texture coordinates differently than glTF. + // |indices_data| is the indices data from the glTF file. |att_id| is the + // attribute id of the texture coordinate attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddTexCoordToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, BuilderT *builder); + + // Copies the mesh feature ID attribute data from |accessor| and adds it to a + // Draco mesh. |indices_data| is the indices data from the glTF file. |att_id| + // is the attribute ID of the mesh feature ID attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddFeatureIdToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, + const std::string &attribute_name, + BuilderT *builder); + + // Copies the attribute data from |accessor| and adds it to a Draco mesh. + // This function will transform all of the data by |transform_matrix| before + // adding the data to the Draco mesh. |indices_data| is the indices data + // from the glTF file. |att_id| is the attribute id of the attribute in the + // Draco mesh. |number_of_elements| is the number of faces or points this + // function will process. |normalize| if set will normalize all of the vector + // data after transformation. |reverse_winding| if set will change the + // orientation of the data. + template + Status AddTransformedDataToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + bool normalize, bool reverse_winding, + BuilderT *builder); + + // Sets values in |data| into the builder |builder| for |att_id|. + template + void SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, bool reverse_winding, + TriangleSoupMeshBuilder *builder); + template + void SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, bool reverse_winding, + PointCloudBuilder *builder); + + // Sets values in |data| into the mesh builder |mb| for |att_id|. + // |reverse_winding| if set will change the orientation of the data. + template + void SetValuesPerFace(const std::vector &indices_data, int att_id, + int number_of_faces, const std::vector &data, + bool reverse_winding, TriangleSoupMeshBuilder *mb); + + // Returns an address pointing to the content stored in |data|. This is used + // when passing values to mesh / point cloud builder when the input type can + // be either a VectorD or an arithmetic type. + template + const void *GetDataContentAddress(const T &data) const; + + // Adds the attribute data in |accessor| to |mb| for unique attribute + // |att_id|. |indices_data| is the mesh's indices data. |reverse_winding| if + // set will change the orientation of the data. + template + Status AddAttributeDataByTypes(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, BuilderT *builder); + + // Adds the textures to |owner|. + template + Status CopyTextures(T *owner); + + // Sets extra attribute properties on a constructed draco mesh. + void SetAttributePropertiesOnDracoMesh(Mesh *mesh); + + // Adds the materials to |mesh|. + Status AddMaterialsToDracoMesh(Mesh *mesh); + + // Adds the material data for the GeometryAttribute::MATERIAL attribute to the + // Draco mesh. + template + Status AddMaterialDataToBuilder(int material_value, int number_of_elements, + BuilderT *builder); + template + Status AddMaterialDataToBuilderInternal(T material_value, int number_of_faces, + TriangleSoupMeshBuilder *builder); + template + Status AddMaterialDataToBuilderInternal(T material_value, + int number_of_points, + PointCloudBuilder *builder); + + // Checks if the glTF file contains a texture. If there is a texture, this + // function will read the texture data and add it to the Draco |material|. If + // there is no texture, this function will return OkStatus(). |texture_info| + // is the data structure containing information about the texture in the glTF + // file. |type| is the type of texture defined by Draco. This is not the same + // as the texture coordinate attribute id. + Status CheckAndAddTextureToDracoMaterial( + int texture_index, int tex_coord_attribute_index, + const tinygltf::ExtensionMap &tex_info_ext, Material *material, + TextureMap::Type type); + + // Decode glTF file to scene. + Status DecodeGltfToScene(); + + // Decode glTF lights into a scene. + Status AddLightsToScene(); + + // Decodes glTF materials variants names into a scene. + Status AddMaterialsVariantsNamesToScene(); + + // Decode glTF animations into a scene. All of the glTF nodes must be decoded + // to the scene before this function is called. + Status AddAnimationsToScene(); + + // Decode glTF node into a Draco scene. |parent_index| is the index of the + // parent node. If |node| is a root node set |parent_index| to + // |kInvalidSceneNodeIndex|. All glTF lights must be decoded to the scene + // before this function is called. + Status DecodeNodeForScene(int node_index, SceneNodeIndex parent_index); + + // Decode glTF primitive into a Draco scene. + Status DecodePrimitiveForScene(const tinygltf::Primitive &primitive, + MeshGroup *mesh_group); + + // Decodes glTF materials variants from |extension| and adds it into materials + // variants |mappings|. Before calling this function, all materials variants + // names must be decoded by calling AddMaterialsVariantsNamesToScene(). + Status DecodeMaterialsVariantsMappings( + const tinygltf::Value::Object &extension, + std::vector *mappings); + + // Decodes glTF mesh feature ID sets from all glTF primitives and adds them to + // |mesh|. + Status AddMeshFeaturesToDracoMesh(Mesh *mesh); + + // Decodes glTF mesh feature ID sets from glTF primitive in glTF node at + // |node_index| and adds them to |mesh|. + Status AddMeshFeaturesToDracoMesh(int node_index, Mesh *mesh); + + // Decodes glTF structural metadata from glTF model and adds it to |geometry|. + template + Status AddStructuralMetadataToGeometry(GeometryT *geometry); + + // Decodes glTF mesh feature ID sets from |primitive| and adds them to |mesh|. + Status DecodeMeshFeatures(const tinygltf::Primitive &primitive, + TextureLibrary *texture_library, Mesh *mesh); + + // Decodes glTF mesh feature ID sets from |extension| and adds them to the + // |mesh_features| vector. + Status DecodeMeshFeatures( + const tinygltf::Value::Object &extension, TextureLibrary *texture_library, + std::vector> *mesh_features); + + // Adds an attribute of type |attribute_name| to |builder|. Returns the + // attribute id. + template + StatusOr AddAttribute(const std::string &attribute_name, + int component_type, int type, BuilderT *builder); + + // Adds an attribute of |attribute_type| to |builder|. Returns the attribute + // id. + template + StatusOr AddAttribute(GeometryAttribute::Type attribute_type, + int component_type, int type, BuilderT *builder); + + // Returns true if the KHR_texture_transform extension is set in |extension|. + // If the KHR_texture_transform extension is set then the values are returned + // in |transform|. + StatusOr CheckKhrTextureTransform( + const tinygltf::ExtensionMap &extension, TextureTransform *transform); + + // Adds glTF material |input_material_index| to |output_material|. + Status AddGltfMaterial(int input_material_index, Material *output_material); + + // Adds unlit property from glTF |input_material| to |output_material|. + void DecodeMaterialUnlitExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds sheen properties from glTF |input_material| to |output_material|. + Status DecodeMaterialSheenExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds transmission from glTF |input_material| to |output_material|. + Status DecodeMaterialTransmissionExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Adds clearcoat properties from glTF |input_material| to |output_material|. + Status DecodeMaterialClearcoatExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Adds volume properties from glTF |input_material| to |output_material|. + Status DecodeMaterialVolumeExtension(const tinygltf::Material &input_material, + int input_material_index, + Material *output_material); + + // Adds ior properties from glTF |input_material| to |output_material|. + Status DecodeMaterialIorExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds specular properties from glTF |input_material| to |output_material|. + Status DecodeMaterialSpecularExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Decodes a float value with |name| from |object| to |value| and returns true + // if a well-formed value with such |name| is present. + static StatusOr DecodeFloat(const std::string &name, + const tinygltf::Value::Object &object, + float *value); + + // Decodes an integer value with |name| from |object| to |value| and returns + // true if a well-formed value with such |name| is present. + static StatusOr DecodeInt(const std::string &name, + const tinygltf::Value::Object &object, + int *value); + + // Decodes a string value with |name| from |object| to |value| and returns + // true if a well-formed value with such |name| is present. + static StatusOr DecodeString(const std::string &name, + const tinygltf::Value::Object &object, + std::string *value); + + // Decodes data and data target from buffer view index with |name| in |object| + // to |data| and returns true if a well-formed data is present. + StatusOr DecodePropertyTableData(const std::string &name, + const tinygltf::Value::Object &object, + PropertyTable::Property::Data *data); + + // Decodes a 3D vector with |name| from |object| to |value| and returns true + // if a well-formed vector with such |name| is present. + static StatusOr DecodeVector3f(const std::string &name, + const tinygltf::Value::Object &object, + Vector3f *value); + + // Decodes a texture with |name| from |object| and adds it to |material| as a + // texture map of |type|. + Status DecodeTexture(const std::string &name, TextureMap::Type type, + const tinygltf::Value::Object &object, + Material *material); + + // Reads texture with |texture_name| from |container_object| into + // |texture_info|. + static Status ParseTextureInfo( + const std::string &texture_name, + const tinygltf::Value::Object &container_object, + tinygltf::TextureInfo *texture_info); + + // Adds the materials to the scene. + Status AddMaterialsToScene(); + + // Adds the skins to the scene. + Status AddSkinsToScene(); + + // All material and non-material textures (e.g., from EXT_mesh_features) are + // initially loaded into a texture library inside the the material library. + // These methods move |non_material_textures| from material texture library + // |material_tl| to non-material texture library |non_material_tl|. + static void MoveNonMaterialTextures(Mesh *mesh); + static void MoveNonMaterialTextures(Scene *scene); + static void MoveNonMaterialTextures( + const std::unordered_set &non_material_textures, + TextureLibrary *material_tl, TextureLibrary *non_material_tl); + + // Builds and returns a mesh constructed from either mesh builder |mb| or + // point cloud builder |pb|. Mesh builder is used if |use_mesh_builder| is set + // to true. + static StatusOr> BuildMeshFromBuilder( + bool use_mesh_builder, TriangleSoupMeshBuilder *mb, + PointCloudBuilder *pb); + + // Map of glTF Mesh to Draco scene mesh group. + std::map gltf_mesh_to_scene_mesh_group_; + + // Data structure that stores the glTF data. + tinygltf::Model gltf_model_; + + // Path to the glTF file. + std::string input_file_name_; + + // Class used to build the Draco mesh. + TriangleSoupMeshBuilder mb_; + PointCloudBuilder pb_; + + // Next face index used when adding attribute data to the Draco mesh. + int next_face_id_; + + // Next point index used when adding attribute data to the point cloud. + int next_point_id_; + + // Total number of indices from all the meshes and primitives. + int total_face_indices_count_; + int total_point_indices_count_; + + // This is the id of the GeometryAttribute::MATERIAL attribute added to the + // Draco mesh. + int material_att_id_; + + // Data used when decoding the entire glTF asset into a single draco::Mesh. + // The struct tracks the total number of elements across all matching + // attributes and it ensures all matching attributes are compatible. + struct MeshAttributeData { + int component_type = 0; + int attribute_type = 0; + bool normalized = false; + int total_attribute_counts = 0; + }; + + // Map of glTF attribute name to attribute component type. + std::map mesh_attribute_data_; + + // Map of glTF attribute name to Draco mesh attribute id. + std::map attribute_name_to_draco_mesh_attribute_id_; + + // Map of glTF material to Draco material index. + std::map gltf_primitive_material_to_draco_material_; + + // Map of glTF material index to transformation scales of primitives. + std::map> gltf_primitive_material_to_scales_; + + // Map of glTF image to Draco textures. + std::map gltf_image_to_draco_texture_; + + std::unique_ptr scene_; + + // Map of glTF Node to local store order. + std::map gltf_node_to_scenenode_index_; + + // Selected mode of the decoded scene graph. + GltfSceneGraphMode gltf_scene_graph_mode_ = GltfSceneGraphMode::TREE; + + // Functionality for deduping primitives on decode. + struct PrimitiveSignature { + const tinygltf::Primitive &primitive; + explicit PrimitiveSignature(const tinygltf::Primitive &primitive) + : primitive(primitive) {} + bool operator==(const PrimitiveSignature &signature) const; + struct Hash { + size_t operator()(const PrimitiveSignature &signature) const; + }; + }; + std::unordered_map + gltf_primitive_to_draco_mesh_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_DECODER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder_test.cc new file mode 100644 index 000000000..fade3ee26 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_decoder_test.cc @@ -0,0 +1,1402 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_decoder.h" + +#include +#include +#include +#include +#include +#include + +#include "draco/material/material_library.h" +#include "draco/scene/mesh_group.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/draco_types.h" +#include "draco/io/gltf_test_helper.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +std::unique_ptr DecodeGltfFile(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + GltfDecoder decoder; + + auto maybe_geometry = decoder.DecodeFromFile(path); + if (!maybe_geometry.ok()) { + return nullptr; + } + std::unique_ptr geometry = std::move(maybe_geometry).value(); + return geometry; +} + +std::unique_ptr DecodeGltfFileToScene(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + GltfDecoder decoder; + + auto maybe_scene = decoder.DecodeFromFileToScene(path); + if (!maybe_scene.ok()) { + return nullptr; + } + std::unique_ptr scene = std::move(maybe_scene).value(); + return scene; +} + +void CompareVectorArray(const std::array &a, + const std::array &b) { + for (int v = 0; v < 3; ++v) { + for (int c = 0; c < 3; ++c) { + EXPECT_FLOAT_EQ(a[v][c], b[v][c]) << "v:" << v << " c:" << c; + } + } +} +} // namespace + +// Tests multiple textures. +TEST(GltfDecoderTest, SphereGltf) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 231) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 224) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); +} + +TEST(GltfDecoderTest, TriangleGltf) { + const std::string file_name = "one_face_123.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. + std::array pos_test; + pos_test[0] = Vector3f(1, 0.0999713, 0); + pos_test[1] = Vector3f(2.00006104, 0.01, 0); + pos_test[2] = Vector3f(3, 0.10998169, 0); + CompareVectorArray(pos, pos_test); +} + +TEST(GltfDecoderTest, MirroredTriangleGltf) { + const std::string file_name = "one_face_123_mirror.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. + std::array pos_test; + pos_test[0] = Vector3f(-1, -0.0999713, 0); + pos_test[1] = Vector3f(-3, -0.10998169, 0); + pos_test[2] = Vector3f(-2.00006104, -0.01, 0); + CompareVectorArray(pos, pos_test); +} + +TEST(GltfDecoderTest, TranslateTriangleGltf) { + const std::string file_name = "one_face_123_translated.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. The glTF file contains a matrix in the main + // node. The matrix defines a translation of (-1.5, 5.0, 2.3). + std::array pos_test; + pos_test[0] = Vector3f(1, 0.0999713, 0); + pos_test[1] = Vector3f(2.00006104, 0.01, 0); + pos_test[2] = Vector3f(3, 0.10998169, 0); + const Vector3f translate(-1.5, 5.0, 2.3); + for (int v = 0; v < 3; ++v) { + pos_test[v] = pos_test[v] + translate; + } + CompareVectorArray(pos, pos_test); +} + +// Tests multiple materials. +TEST(GltfDecoderTest, MilkTruckGltf) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3564) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 3624) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->NumTextureMaps(), 0); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(2)->NumTextureMaps(), 0); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(3)->NumTextureMaps(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetName(), "truck"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetName(), "glass"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(2)->GetName(), + "window_trim"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(3)->GetName(), "wheels"); +} + +TEST(GltfDecoderTest, SceneMilkTruckGltf) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(scene->NumNodes(), 5); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->NumLights(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->NumTextureMaps(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(2)->NumTextureMaps(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(3)->NumTextureMaps(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetName(), "truck"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetName(), "glass"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(2)->GetName(), + "window_trim"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(3)->GetName(), "wheels"); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 0); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 2); + ASSERT_EQ(animation->NumChannels(), 2); + } + + ASSERT_EQ(scene->GetMeshGroup(MeshGroupIndex(0))->GetName(), + "Cesium_Milk_Truck"); + ASSERT_EQ(scene->GetMeshGroup(MeshGroupIndex(1))->GetName(), "Wheels"); + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +TEST(GltfDecoderTest, AnimatedBonesGltf) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 22); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 57); + ASSERT_EQ(animation->NumChannels(), 57); + } + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +TEST(GltfDecoderTest, AnimatedBonesGlb) { + const std::string file_name = "CesiumMan/glTF_Binary/CesiumMan.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 22); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 57); + ASSERT_EQ(animation->NumChannels(), 57); + } + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +// Tests multiple primitives with the same material index. +TEST(GltfDecoderTest, LanternGltf) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 4145) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 5394) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 4); +} + +// Tests COLOR_0 input attribute. +TEST(GltfDecoderTest, ColorAttributeGltf) { + const std::string file_name = "test_pos_color.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 2) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 114) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 224) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + ASSERT_NE(mesh->GetNamedAttribute(GeometryAttribute::COLOR), nullptr); + ASSERT_EQ(mesh->GetNamedAttribute(GeometryAttribute::COLOR)->data_type(), + draco::DT_UINT8); + // Ensure the normalized property for the color attribute is set properly. + ASSERT_TRUE(mesh->GetNamedAttribute(GeometryAttribute::COLOR)->normalized()); +} + +// Tests COLOR_0 input attribute when the asset is loaded into a scene. +TEST(GltfDecoderTest, ColorAttributeGltfScene) { + const std::string file_name = "test_pos_color.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_EQ(scene->NumMeshes(), 1); + const Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_NE(mesh.GetNamedAttribute(GeometryAttribute::COLOR), nullptr); + ASSERT_EQ(mesh.GetNamedAttribute(GeometryAttribute::COLOR)->data_type(), + draco::DT_UINT8); + // Ensure the normalized property for the color attribute is set properly. + ASSERT_TRUE(mesh.GetNamedAttribute(GeometryAttribute::COLOR)->normalized()); +} + +// Tests a mesh with two sets of texture coordinates. +TEST(GltfDecoderTest, TwoTexCoordAttributesGltf) { + const std::string file_name = "sphere_two_tex_coords.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); +} + +// Tests an input with a valid tangent attribute does not auto generate the +// tangent attribute. +TEST(GltfDecoderTest, TestSceneWithTangents) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Ensure no mesh has auto-generated tangents (and that some meshes have the + // tangent attribute). + int num_tangent_attributes = 0; + for (MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + if (scene->GetMesh(mi).GetNamedAttribute(GeometryAttribute::TANGENT) != + nullptr) { + num_tangent_attributes++; + ASSERT_FALSE(MeshUtils::HasAutoGeneratedTangents(scene->GetMesh(mi))); + } + } + ASSERT_GT(num_tangent_attributes, 0); +} + +// Tests an input file where multiple textures share the same image asset. +TEST(GltfDecoderTest, SharedImages) { + const std::string file_name = "SphereAllSame/sphere_texture_all.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 5); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 4); +} + +TEST(GltfDecoderTest, TextureNamesAreNotEmpty) { + const std::string file_name = "SphereAllSame/sphere_texture_all.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 5); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 4); + const std::vector textures = { + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(1), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(2), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(3)}; + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[0]), "256x256_all_orange"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[1]), "256x256_all_blue"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[2]), "256x256_all_red"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[3]), "256x256_all_green"); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[0]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[1]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[2]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[3]), ImageFormat::PNG); +} + +TEST(GltfDecoderTest, TestTexCoord1) { + const std::string file_name = "MultiUVTest/glTF/MultiUVTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 2); + const std::vector textures = { + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(1)}; + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[0]), "uv0"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[1]), "uv1"); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[0]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[1]), ImageFormat::PNG); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::POSITION), 1); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TANGENT), 1); +} + +TEST(GltfDecoderTest, SimpleScene) { + const std::string file_name = "Box/glTF/Box.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 2); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + ASSERT_EQ(scene->NumSkins(), 0); + ASSERT_EQ(scene->NumAnimations(), 0); + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } + + // Check names of nodes are empty. + EXPECT_TRUE(scene->GetNode(SceneNodeIndex(0))->GetName().empty()); + EXPECT_TRUE(scene->GetNode(SceneNodeIndex(1))->GetName().empty()); +} + +TEST(GltfDecoderTest, LanternScene) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 3); + EXPECT_EQ(scene->NumMeshGroups(), 3); + EXPECT_EQ(scene->NumNodes(), 4); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 4); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), + false); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); + + // Check names of nodes have been populated. + EXPECT_EQ(scene->GetNode(SceneNodeIndex(0))->GetName(), "Lantern"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(1))->GetName(), "LanternPole_Body"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(2))->GetName(), "LanternPole_Chain"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(3))->GetName(), + "LanternPole_Lantern"); +} + +TEST(GltfDecoderTest, SimpleTriangleMesh) { + const std::string file_name = "Triangle/glTF/Triangle.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 0); +} + +TEST(GltfDecoderTest, SimpleTriangleScene) { + const std::string file_name = "Triangle/glTF/Triangle.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 1); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); +} + +TEST(GltfDecoderTest, ThreeMeshesOneNoMaterialScene) { + const std::string file_name = + "three_meshes_two_materials_one_no_material/" + "three_meshes_two_materials_one_no_material.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 3); + EXPECT_EQ(scene->NumMeshGroups(), 3); + EXPECT_EQ(scene->NumNodes(), 4); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 3); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); +} + +TEST(GltfDecoderTest, ThreeMeshesOneNoMaterialMesh) { + const std::string file_name = + "three_meshes_two_materials_one_no_material/" + "three_meshes_two_materials_one_no_material.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 72) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 36) << "Unexpected number of faces."; + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 3); +} + +TEST(GltfDecoderTest, DoubleSidedMaterial) { + const std::string file_name = "TwoSidedPlane/glTF/TwoSidedPlane.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); + + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); +} + +TEST(GltfDecoderTest, VertexColorTest) { + const std::string file_name = "VertexColorTest/glTF/VertexColorTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(mesh->NumNamedAttributes(GeometryAttribute::COLOR), 1); + + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(scene->NumMeshes(), 2); + const Mesh &second_mesh = scene->GetMesh(MeshIndex(1)); + EXPECT_EQ(second_mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); +} + +TEST(GltfDecoderTest, MorphTargets) { + const std::string filename = + "KhronosSampleModels/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, SparseAccessors) { + const std::string filename = + "KhronosSampleModels/SimpleSparseAccessor/glTF/SimpleSparseAccessor.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, PbrSpecularGlossinessExtension) { + const std::string filename = + "KhronosSampleModels/SpecGlossVsMetalRough/glTF/" + "SpecGlossVsMetalRough.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, DifferentWrappingModes) { + const std::string filename = + "KhronosSampleModels/TextureSettingsTest/glTF/TextureSettingsTest.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_TRUE(maybe_scene.ok()); + const draco::Scene &scene = *maybe_scene.value(); + ASSERT_EQ(scene.GetMaterialLibrary().GetTextureLibrary().NumTextures(), 3); + ASSERT_EQ(scene.GetMaterialLibrary().NumMaterials(), 10); + const draco::Material &material = *scene.GetMaterialLibrary().GetMaterial(0); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_EQ(material.GetTextureMapByIndex(0)->wrapping_mode().s, + draco::TextureMap::REPEAT); + ASSERT_EQ(material.GetTextureMapByIndex(0)->wrapping_mode().t, + draco::TextureMap::MIRRORED_REPEAT); +} + +TEST(GltfDecoderTest, KhrMaterialsUnlitExtension) { + const std::string no_unlit_filename = "Box/glTF/Box.gltf"; + const std::unique_ptr scene_no_unlit( + DecodeGltfFileToScene(no_unlit_filename)); + EXPECT_EQ(scene_no_unlit->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene_no_unlit->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), + false); + + const std::string filename = + "KhronosSampleModels/UnlitTest/glTF/UnlitTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(filename)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), true); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetUnlit(), true); + + const std::unique_ptr scene(DecodeGltfFileToScene(filename)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), true); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetUnlit(), true); +} + +TEST(GltfDecoderTest, KhrMaterialsSheenExtension) { + // Check that a model with no sheen is loaded with no sheen. + { + const std::unique_ptr scene( + DecodeGltfFileToScene("Box/glTF/Box.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + + // Check that material has no sheen. + const Material &material = *scene->GetMaterialLibrary().GetMaterial(0); + EXPECT_FALSE(material.HasSheen()); + + // Check that sheen color and roughness factors have default values. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(0.f, 0.f, 0.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 0.f); + + // Check that sheen textures are absent. + EXPECT_EQ(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_EQ(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + } + + // Check that a model with sheen is loaded as a mesh with sheen. + { + // Load model as a mesh. + const std::unique_ptr mesh( + DecodeGltfFile("KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf")); + EXPECT_NE(mesh, nullptr); + const Material &material = *mesh->GetMaterialLibrary().GetMaterial(0); + + // Check that material has sheen. + EXPECT_TRUE(material.HasSheen()); + + // Check that sheen color and roughness factors are present. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(1.f, 1.f, 1.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 1.f); + + // Check that sheen color and roughness textures are present. + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + + // Check that sheen color and roughness textures are shared. + EXPECT_EQ( + material.GetTextureMapByType(TextureMap::SHEEN_COLOR)->texture(), + material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS)->texture()); + } + + // Check that a model with sheen is loaded as a scene with sheen. + { + // Load model as a scene. + const std::unique_ptr scene(DecodeGltfFileToScene( + "KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + const Material &material = *scene->GetMaterialLibrary().GetMaterial(0); + + // Check that material has sheen. + EXPECT_TRUE(material.HasSheen()); + + // Check that sheen color and roughness factors are present. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(1.f, 1.f, 1.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 1.f); + + // Check that sheen color and roughness textures are present. + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + + // Check that sheen color and roughness textures are shared. + EXPECT_EQ( + material.GetTextureMapByType(TextureMap::SHEEN_COLOR)->texture(), + material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS)->texture()); + } +} + +TEST(GltfDecoderTest, PbrNextExtensions) { + // Check that a model with no material extensions is loaded correctly. + { + const std::unique_ptr scene( + DecodeGltfFileToScene("Box/glTF/Box.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + const Material &m = *scene->GetMaterialLibrary().GetMaterial(0); + + // Check that material has no extensions. + EXPECT_FALSE(m.HasSheen()); + EXPECT_FALSE(m.HasTransmission()); + EXPECT_FALSE(m.HasClearcoat()); + EXPECT_FALSE(m.HasVolume()); + EXPECT_FALSE(m.HasIor()); + EXPECT_FALSE(m.HasSpecular()); + } + + // Check that a model with material extensions is loaded correctly. + { + const std::unique_ptr mesh( + DecodeGltfFile("pbr_next/sphere/glTF/sphere.gltf")); + EXPECT_NE(mesh, nullptr); + const Material &m = *mesh->GetMaterialLibrary().GetMaterial(0); + + // Check that material has extensions. + EXPECT_TRUE(m.HasSheen()); + EXPECT_TRUE(m.HasTransmission()); + EXPECT_TRUE(m.HasClearcoat()); + EXPECT_TRUE(m.HasVolume()); + EXPECT_TRUE(m.HasIor()); + EXPECT_TRUE(m.HasSpecular()); + + // Check that material has correct extension properties. + EXPECT_EQ(m.GetSheenColorFactor(), Vector3f(1.0f, 0.329f, 0.1f)); + EXPECT_EQ(m.GetSheenRoughnessFactor(), 0.8f); + EXPECT_EQ(m.GetTransmissionFactor(), 0.75f); + EXPECT_EQ(m.GetClearcoatFactor(), 0.95f); + EXPECT_EQ(m.GetClearcoatRoughnessFactor(), 0.03f); + EXPECT_EQ(m.GetAttenuationColor(), Vector3f(0.921f, 0.640f, 0.064f)); + EXPECT_EQ(m.GetAttenuationDistance(), 0.155f); + EXPECT_EQ(m.GetThicknessFactor(), 2.27f); + EXPECT_EQ(m.GetIor(), 1.55f); + EXPECT_EQ(m.GetSpecularFactor(), 0.3f); + EXPECT_EQ(m.GetSpecularColorFactor(), Vector3f(0.212f, 0.521f, 0.051f)); + + // Check that material has all extension textures. + EXPECT_NE(m.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::TRANSMISSION), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT_ROUGHNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT_NORMAL), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::THICKNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SPECULAR), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SPECULAR_COLOR), nullptr); + } +} + +TEST(GltfDecoderTest, TextureTransformTest) { + const std::string filename = + "KhronosSampleModels/TextureTransformTest/glTF/TextureTransformTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(filename)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 9); + for (int i = 0; i < 6; ++i) { + EXPECT_FALSE(TextureTransform::IsDefault(mesh->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + for (int i = 6; i < 9; ++i) { + EXPECT_TRUE(TextureTransform::IsDefault(mesh->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + + const std::unique_ptr scene(DecodeGltfFileToScene(filename)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 9); + for (int i = 0; i < 6; ++i) { + EXPECT_FALSE(TextureTransform::IsDefault(scene->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + for (int i = 6; i < 9; ++i) { + EXPECT_TRUE(TextureTransform::IsDefault(scene->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } +} + +TEST(GltfDecoderTest, GlbTextureSource) { + const std::string file_name = "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 3); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + EXPECT_EQ(scene->NumAnimations(), 0); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 1); + const Texture *const texture = + scene->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + const SourceImage &source_image = texture->source_image(); + EXPECT_EQ(source_image.encoded_data().size(), 16302); + EXPECT_EQ(source_image.filename(), ""); + EXPECT_EQ(source_image.mime_type(), "image/png"); +} + +TEST(GltfDecoderTest, GltfTextureSource) { + const std::string file_name = "KhronosSampleModels/Duck/glTF/Duck.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 3); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + EXPECT_EQ(scene->NumAnimations(), 0); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 1); + const Texture *const texture = + scene->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + const SourceImage &source_image = texture->source_image(); + EXPECT_EQ(source_image.encoded_data().size(), 0); + EXPECT_FALSE(source_image.filename().empty()); + EXPECT_EQ(source_image.mime_type(), ""); +} + +TEST(GltfDecoderTest, GltfDecodeWithDraco) { + // Tests that we can decode a glTF containing Draco compressed geometry. + const std::string file_name = "Box/glTF_Binary/Box.glb"; + const std::string file_name_with_draco = "Box/glTF_Binary/Box_Draco.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + const std::unique_ptr scene_draco( + DecodeGltfFileToScene(file_name_with_draco)); + ASSERT_NE(scene, nullptr); + ASSERT_NE(scene_draco, nullptr); + EXPECT_EQ(scene->NumMeshes(), scene_draco->NumMeshes()); + EXPECT_EQ(scene->NumMeshGroups(), scene_draco->NumMeshGroups()); + EXPECT_EQ(scene->NumNodes(), scene_draco->NumNodes()); + EXPECT_EQ(scene->NumRootNodes(), scene_draco->NumRootNodes()); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), + scene_draco->GetMaterialLibrary().NumMaterials()); + EXPECT_EQ(scene->NumAnimations(), scene_draco->NumAnimations()); + EXPECT_EQ(scene->NumSkins(), scene_draco->NumSkins()); + + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->GetMesh(draco::MeshIndex(0)).num_faces(), + scene_draco->GetMesh(draco::MeshIndex(0)).num_faces()); +} + +TEST(GltfDecoderTest, TestAnimationNames) { + const std::string file_name = "InterpolationTest/glTF/InterpolationTest.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + EXPECT_EQ(scene->NumAnimations(), 9); + + const std::vector animation_names{ + "Step Scale", "Linear Scale", + "CubicSpline Scale", "Step Rotation", + "CubicSpline Rotation", "Linear Rotation", + "Step Translation", "CubicSpline Translation", + "Linear Translation"}; + for (int i = 0; i < scene->NumAnimations(); ++i) { + const Animation *const anim = scene->GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + ASSERT_EQ(anim->GetName(), animation_names[i]); + } +} + +TEST(GltfDecoderTest, DuplicatePrimitives) { + const std::string file_name = "DuplicateMeshes/duplicate_meshes.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // There should be only one unique base mesh in the scene and four mesh + // groups (instances). + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 4); + + // There should be two materials used by the instances. + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); +} + +TEST(GltfDecoderTest, SimpleSkin) { + // This is a simple skin example from glTF tutorial. + const std::string file_name = "simple_skin.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Check scene size. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(scene->GetMeshGroup(draco::MeshGroupIndex(0))->NumMeshInstances(), + 1); + ASSERT_EQ(scene->NumNodes(), 3); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + + // Check animation size. + const Animation *const animation = scene->GetAnimation(AnimationIndex(0)); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 1); + ASSERT_EQ(animation->NumChannels(), 1); + ASSERT_EQ(animation->NumNodeAnimationData(), 2); + + // Check animation sampler. + const AnimationSampler *const sampler = animation->GetSampler(0); + ASSERT_NE(sampler, nullptr); + ASSERT_EQ(sampler->input_index, 0); + ASSERT_EQ(sampler->interpolation_type, + AnimationSampler::SamplerInterpolation::LINEAR); + ASSERT_EQ(sampler->output_index, 1); + + // Check animation channel. + const AnimationChannel *const channel = animation->GetChannel(0); + ASSERT_NE(channel, nullptr); + ASSERT_EQ(channel->sampler_index, 0); + ASSERT_EQ(channel->target_index, 2); + ASSERT_EQ(channel->transformation_type, + AnimationChannel::ChannelTransformation::ROTATION); + + // Check the first node animation data. + { + const NodeAnimationData *const node_animation = + animation->GetNodeAnimationData(0); + ASSERT_EQ(node_animation->ComponentSize(), 4); + ASSERT_EQ(node_animation->NumComponents(), 1); + ASSERT_EQ(node_animation->count(), 12); + ASSERT_EQ(node_animation->type(), NodeAnimationData::Type::SCALAR); + ASSERT_FALSE(node_animation->normalized()); + const std::vector &node_animation_data = *node_animation->GetData(); + const std::vector expected_node_animation_data{ + 0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f}; + ASSERT_EQ(node_animation_data, expected_node_animation_data); + } + + // Check the second node animation data. + { + const NodeAnimationData *const node_animation = + animation->GetNodeAnimationData(1); + ASSERT_EQ(node_animation->ComponentSize(), 4); + ASSERT_EQ(node_animation->NumComponents(), 4); + ASSERT_EQ(node_animation->count(), 12); + ASSERT_EQ(node_animation->type(), NodeAnimationData::Type::VEC4); + ASSERT_FALSE(node_animation->normalized()); + const std::vector &node_animation_data = *node_animation->GetData(); + std::cout << std::endl; + // clang-format off + const std::vector expected_node_animation_data{ + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, 0.383f, 0.924f, + 0.000f, 0.000f, 0.707f, 0.707f, + 0.000f, 0.000f, 0.707f, 0.707f, + 0.000f, 0.000f, 0.383f, 0.924f, + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, -0.383f, 0.924f, + 0.000f, 0.000f, -0.707f, 0.707f, + 0.000f, 0.000f, -0.707f, 0.707f, + 0.000f, 0.000f, -0.383f, 0.924f, + 0.000f, 0.000f, 0.000f, 1.000f}; + // clang-format on + ASSERT_EQ(node_animation_data, expected_node_animation_data); + } + + // Check skin. + const Skin *const skin = scene->GetSkin(SkinIndex(0)); + ASSERT_NE(skin, nullptr); + ASSERT_EQ(skin->NumJoints(), 2); + ASSERT_EQ(skin->GetJointRoot(), kInvalidSceneNodeIndex); + ASSERT_EQ(skin->GetJoint(0), SceneNodeIndex(1)); + ASSERT_EQ(skin->GetJoint(1), SceneNodeIndex(2)); + + // Check inverse bind matrices. + const NodeAnimationData &bind_matrices = skin->GetInverseBindMatrices(); + ASSERT_EQ(bind_matrices.type(), NodeAnimationData::Type::MAT4); + ASSERT_EQ(bind_matrices.count(), 2); + ASSERT_EQ(bind_matrices.normalized(), false); + ASSERT_NE(bind_matrices.GetData(), nullptr); + const std::vector &bind_matrices_data = *bind_matrices.GetData(); + // clang-format off + const std::vector expected_bind_matrices_data{ + // First matrix. + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -1.0f, 0.0f, 1.0f, + // Second matrix. + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -1.0f, 0.0f, 1.0f}; + // clang-format on + ASSERT_EQ(bind_matrices_data, expected_bind_matrices_data); + + // Check mesh size. + const Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(mesh.num_faces(), 8); + ASSERT_EQ(mesh.num_points(), 10); + ASSERT_EQ(mesh.num_attributes(), 3); + + // Check vertex joint indices. + const PointAttribute *const joints_att = + mesh.GetNamedAttribute(GeometryAttribute::JOINTS); + ASSERT_NE(joints_att, nullptr); + ASSERT_EQ(joints_att->data_type(), DT_UINT16); + ASSERT_EQ(joints_att->num_components(), 4); + ASSERT_EQ(joints_att->size(), 1); + // clang-format off + const std::array expected_joints = { + // Each vertex is associated with four joints. + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0 }; + // clang-format on + std::array joints; + for (draco::PointIndex pi(0); pi < mesh.num_points(); ++pi) { + joints_att->GetMappedValue(pi, &joints[4 * pi.value()]); + } + ASSERT_EQ(joints, expected_joints); + + // Check vertex joint weights. + const PointAttribute *const weights_att = + mesh.GetNamedAttribute(GeometryAttribute::WEIGHTS); + ASSERT_NE(weights_att, nullptr); + ASSERT_EQ(weights_att->data_type(), DT_FLOAT32); + ASSERT_EQ(weights_att->num_components(), 4); + ASSERT_EQ(weights_att->size(), 5); + // clang-format off + const std::array expected_weights = { + // Each vertex has four joint weights. + 1.00f, 0.00f, 0.00f, 0.00f, + 1.00f, 0.00f, 0.00f, 0.00f, + 0.75f, 0.25f, 0.00f, 0.00f, + 0.75f, 0.25f, 0.00f, 0.00f, + 0.50f, 0.50f, 0.00f, 0.00f, + 0.50f, 0.50f, 0.00f, 0.00f, + 0.25f, 0.75f, 0.00f, 0.00f, + 0.25f, 0.75f, 0.00f, 0.00f, + 0.00f, 1.00f, 0.00f, 0.00f, + 0.00f, 1.00f, 0.00f, 0.00f }; + // clang-format on + std::array weights; + for (draco::PointIndex pi(0); pi < mesh.num_points(); ++pi) { + weights_att->GetMappedValue(pi, &weights[4 * pi.value()]); + } + ASSERT_EQ(weights, expected_weights); +} + +TEST(GltfDecoderTest, DecodeMeshWithImplicitPrimitiveIndices) { + // Check that glTF primitives with implicit indices can be loaded as a mesh. + const std::string file_name = "Fox/glTF/Fox.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 576); +} + +TEST(GltfDecoderTest, DecodeSceneWithImplicitPrimitiveIndices) { + // Check that glTF primitives with implicit indices can be loaded as a scene. + const std::string file_name = "Fox/glTF/Fox.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).num_faces(), 576); +} + +TEST(GltfDecoderTest, DecodeFromBufferToMesh) { + // Checks that a mesh can be decoded from buffer in GLB format. + // Read GLB file contents into a buffer. + const std::string file_name = "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::string file_path = GetTestFileFullPath(file_name); + std::vector file_data; + ASSERT_TRUE(ReadFileToBuffer(file_path, &file_data)); + DecoderBuffer buffer; + buffer.Init(file_data.data(), file_data.size()); + + // Decode mesh from buffer. + GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromBuffer(&buffer)); + ASSERT_NE(mesh, nullptr); + + // Decode mesh from GLB file. + const std::unique_ptr expected_mesh(DecodeGltfFile(file_name)); + ASSERT_NE(expected_mesh, nullptr); + + // Check that meshes decoded from the buffer and from GLB file are equivalent. + MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, *expected_mesh)); +} + +TEST(GltfDecoderTest, DecodeGraph) { + // Checks that we can decode a scene with a general graph structure where a + // node has multiple parents. + // The input model has one root node, 4 children nodes that all point to a + // single node that contains the cube mesh. + const std::string file_name = "CubeScaledInstances/glTF/cube_att.gltf"; + const std::string file_path = GetTestFileFullPath(file_name); + + // First decode the scene into a tree-graph. + draco::GltfDecoder dec_tree; + DRACO_ASSIGN_OR_ASSERT(auto scene_tree, + dec_tree.DecodeFromFileToScene(file_path)); + // We expect to have 9 nodes with 4 mesh instances. The leaf node with the + // cube is duplicated 4 times, once for each instance. + ASSERT_EQ(scene_tree->NumNodes(), 9); + auto instances_tree = draco::SceneUtils::ComputeAllInstances(*scene_tree); + ASSERT_EQ(instances_tree.size(), 4); + + // Decode the scene into a scene-graph. + draco::GltfDecoder dec_graph; + dec_graph.SetSceneGraphMode(draco::GltfDecoder::GltfSceneGraphMode::DAG); + DRACO_ASSIGN_OR_ASSERT(auto scene_graph, + dec_graph.DecodeFromFileToScene(file_path)); + + // We expect to have 6 nodes with 4 mesh instances. The leaf node is shared + // for all mesh instances. + ASSERT_EQ(scene_graph->NumNodes(), 6); + auto instances_graph = draco::SceneUtils::ComputeAllInstances(*scene_graph); + ASSERT_EQ(instances_graph.size(), 4); + + // Check that all instances share the same scene node. + for (draco::MeshInstanceIndex mii(1); mii < 4; ++mii) { + ASSERT_EQ(instances_graph[mii - 1].scene_node_index, + instances_graph[mii].scene_node_index); + } +} + +TEST(GltfDecoderTest, CorrectVolumeThicknessFactor) { + // Checks that when a model is decoded as draco::Mesh the PBR material volume + // thickness factor is corrected according to geometry transformation scale in + // the scene graph. + constexpr float kDragonScale = 0.25f; + constexpr float kDragonVolumeThickness = 2.27f; + + // Read model as draco::Scene and check dragon mesh transformation scale and + // its PBR material volume thickness factor. + const std::unique_ptr scene = draco::ReadSceneFromTestFile( + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"); + ASSERT_NE(scene, nullptr); + auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 2); + ASSERT_EQ(instances[MeshInstanceIndex(0)].transform.col(0).norm(), + kDragonScale); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetThicknessFactor(), + kDragonVolumeThickness); + + // Read model as draco::Mesh and check corrected volume thickness factor. + const std::unique_ptr mesh = draco::ReadMeshFromTestFile( + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetThicknessFactor(), + kDragonScale * kDragonVolumeThickness); +} + +TEST(GltfDecoderTest, DecodeLightsIntoMesh) { + // Checks that a model with lights can be decoded into draco::Mesh with the + // lights discarded. + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 224); +} + +TEST(GltfDecoderTest, DecodeLightsIntoScene) { + // Checks that a model with lights can be decoded into draco::Scene. + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumLights(), 4); + + // Check spot light with all properties specified. + Light &light = *scene->GetLight(LightIndex(0)); + ASSERT_EQ(light.GetName(), "Blue Lightsaber"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.72f, 0.71f, 1.00f)); + ASSERT_EQ(light.GetIntensity(), 3.0); + ASSERT_EQ(light.GetType(), draco::Light::SPOT); + ASSERT_EQ(light.GetRange(), 100); + ASSERT_EQ(light.GetInnerConeAngle(), 0.2); + ASSERT_EQ(light.GetOuterConeAngle(), 0.8); + + // Check point light with all properties specified. + light = *scene->GetLight(LightIndex(1)); + ASSERT_EQ(light.GetName(), "The Star of Earendil"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.90f, 0.97f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 5.0); + ASSERT_EQ(light.GetType(), draco::Light::POINT); + ASSERT_EQ(light.GetRange(), 1000); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_NEAR(light.GetOuterConeAngle(), DRACO_PI / 4.0f, 1e-8); + + // Check directional light with some properties specified. + light = *scene->GetLight(LightIndex(2)); + ASSERT_EQ(light.GetName(), "Arc Reactor"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.9f, 0.9, 0.9f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::DIRECTIONAL); + ASSERT_EQ(light.GetRange(), 200.0); + + // Check spot light with no properties specified. + light = *scene->GetLight(LightIndex(3)); + ASSERT_EQ(light.GetName(), ""); + ASSERT_EQ(light.GetColor(), draco::Vector3f(1.0f, 1.0f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::SPOT); + ASSERT_EQ(light.GetRange(), std::numeric_limits::max()); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_NEAR(light.GetOuterConeAngle(), DRACO_PI / 4.0f, 1e-8); + + // Check that lights are referenced by the scene nodes. + ASSERT_EQ(scene->GetNode(SceneNodeIndex(0))->GetLightIndex(), + kInvalidLightIndex); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(1))->GetLightIndex(), LightIndex(0)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(2))->GetLightIndex(), LightIndex(2)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(3))->GetLightIndex(), LightIndex(3)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(4))->GetLightIndex(), LightIndex(1)); +} + +TEST(GltfDecoderTest, MaterialsVariants) { + // Checks that a model with KHR_materials_variants extension can be decoded. + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, + decoder.DecodeFromFileToScene(GetTestFileFullPath( + "KhronosSampleModels/DragonAttenuation/glTF/" + "DragonAttenuation.gltf"))); + ASSERT_NE(scene, nullptr); + const draco::MaterialLibrary &library = scene->GetMaterialLibrary(); + ASSERT_EQ(library.NumMaterialsVariants(), 2); + ASSERT_EQ(library.GetMaterialsVariantName(0), "Attenuation"); + ASSERT_EQ(library.GetMaterialsVariantName(1), "Surface Color"); + + // Check that the cloth mesh has no material variants. + const draco::MeshGroup &cloth_group = + *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(cloth_group.GetName(), "Cloth Backdrop"); + ASSERT_EQ(cloth_group.NumMeshInstances(), 1); + const auto &cloth_mappings = + cloth_group.GetMeshInstance(0).materials_variants_mappings; + ASSERT_EQ(cloth_mappings.size(), 0); + + // Check that the dragon has correct materials variants. + const draco::MeshGroup &dragon_group = + *scene->GetMeshGroup(draco::MeshGroupIndex(1)); + ASSERT_EQ(dragon_group.GetName(), "Dragon"); + ASSERT_EQ(dragon_group.NumMeshInstances(), 1); + const auto &dragon_mappings = + dragon_group.GetMeshInstance(0).materials_variants_mappings; + ASSERT_EQ(dragon_mappings.size(), 2); + ASSERT_EQ(dragon_mappings[0].material, 1); + ASSERT_EQ(dragon_mappings[1].material, 2); + ASSERT_EQ(dragon_mappings[0].variants.size(), 1); + ASSERT_EQ(dragon_mappings[1].variants.size(), 1); + ASSERT_EQ(dragon_mappings[0].variants[0], 0); + ASSERT_EQ(dragon_mappings[1].variants[0], 1); +} + +TEST(GltfDecoderTest, DecodeMeshWithMeshFeaturesWithStructuralMetadata) { + // Checks decoding of a simple glTF with mesh features and structural metadata + // property table as draco::Mesh. + constexpr bool kDracoCompressionEnabled = false; + const auto path = GetTestFileFullPath("BoxMeta/glTF/BoxMeta.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh); +} + +TEST(GltfDecoderTest, DecodeMeshWithMeshFeaturesWithDracoCompression) { + // Checks decoding of a simple glTF with mesh features compressed with Draco + // as draco::Mesh. + constexpr bool kDracoCompressionEnabled = true; + const auto path = GetTestFileFullPath("BoxMetaDraco/glTF/BoxMetaDraco.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled); +} + +TEST(GltfDecoderTest, DecodeSceneWithMeshFeaturesWithStructuralMetadata) { + // Checks decoding of a simple glTF with mesh features and structural metadata + // property table as draco::Scene. + constexpr bool kHasDracoCompression = false; + const auto path = GetTestFileFullPath("BoxMeta/glTF/BoxMeta.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene); +} + +TEST(GltfDecoderTest, DecodeSceneWithMeshFeaturesWithDracoCompression) { + // Checks decoding of a simple glTF with mesh features compressed with Draco + // as draco::Scene. + constexpr bool kHasDracoCompression = true; + const auto path = GetTestFileFullPath("BoxMetaDraco/glTF/BoxMetaDraco.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression); +} + +TEST(GltfDecoderTest, DecodePointCloudToMesh) { + // Checks decoding of a simple glTF with point primitives (no meshes). + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + + // Check the point cloud has expected number of points and attributes. + ASSERT_EQ(mesh->num_faces(), 0); + ASSERT_EQ(mesh->num_points(), 462); + + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::MATERIAL), 1); + + // Check the point cloud has two materials. + ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL)->size(), + 2); +} + +TEST(GltfDecoderTest, DecodeMeshAndPointCloudToMesh) { + // Checks decoding of a simple glTF with a mesh and point primitives into + // draco::Mesh. This should fail (draco::Mesh can't support mixed primitives). + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_mesh_and_point_cloud.gltf"); + draco::GltfDecoder decoder; + ASSERT_FALSE(decoder.DecodeFromFile(path).ok()); +} + +TEST(GltfDecoderTest, DecodePointCloudToScene) { + // Checks decoding of a simple glTF with point primitives (no meshes) into + // draco::Scene. + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + + ASSERT_EQ(scene->NumMeshes(), 2); + + // Check that each point cloud has expected number of points and attributes. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + const auto &mesh = scene->GetMesh(mi); + ASSERT_EQ(mesh.num_faces(), 0); + ASSERT_EQ(mesh.num_points(), 231); + + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::MATERIAL), 0); + } + + // Check the materials are properly assigned to each point cloud. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 2); + ASSERT_EQ(draco::SceneUtils::GetMeshInstanceMaterialIndex( + *scene, instances[draco::MeshInstanceIndex(0)]), + 0); + ASSERT_EQ(draco::SceneUtils::GetMeshInstanceMaterialIndex( + *scene, instances[draco::MeshInstanceIndex(1)]), + 1); +} + +TEST(GltfDecoderTest, DecodeMeshAndPointCloudToScene) { + // Checks decoding of a simple glTF with a mesh and point primitives into + // draco::Scene. + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_mesh_and_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + + ASSERT_EQ(scene->NumMeshes(), 2); + + // First mesh should be a real mesh while the other one should be a point + // cloud (no faces). Otherwise, they should have the same properties. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + const auto &mesh = scene->GetMesh(mi); + ASSERT_EQ(mesh.num_faces(), mi.value() == 0 ? 224 : 0); + ASSERT_EQ(mesh.num_points(), 231); + + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + } +} + +TEST(GltfDecoderTest, TestLoadUnsupportedTexCoordAttributes) { + // Checks that unsupported attributes (TEXCOORD_2 ... TEXCOORD_7) are ignored + // without causing the decoder to fail. + auto scene = draco::ReadSceneFromTestFile("UnusedTexCoords/TexCoord2.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.cc new file mode 100644 index 000000000..0509b588f --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.cc @@ -0,0 +1,3662 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_encoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "draco/attributes/geometry_attribute.h" +#include "draco/attributes/point_attribute.h" +#include "draco/compression/draco_compression_options.h" +#include "draco/compression/expert_encode.h" +#include "draco/core/draco_types.h" +#include "draco/core/vector_d.h" +#include "draco/io/file_utils.h" +#include "draco/io/file_writer_utils.h" +#include "draco/io/gltf_utils.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_splitter.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/instance_array.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +// Values are specfified from glTF 2.0 sampler spec. See here for more +// information: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sampler +int TextureFilterTypeToGltfValue(TextureMap::FilterType filter_type) { + switch (filter_type) { + case TextureMap::NEAREST: + return 9728; + case TextureMap::LINEAR: + return 9729; + case TextureMap::NEAREST_MIPMAP_NEAREST: + return 9984; + case TextureMap::LINEAR_MIPMAP_NEAREST: + return 9985; + case TextureMap::NEAREST_MIPMAP_LINEAR: + return 9986; + case TextureMap::LINEAR_MIPMAP_LINEAR: + return 9987; + default: + return -1; + } +} + +// Values are specfified from glTF 2.0 sampler spec. See here for more +// information: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sampler +int TextureAxisWrappingModeToGltfValue(TextureMap::AxisWrappingMode mode) { + switch (mode) { + case TextureMap::CLAMP_TO_EDGE: + return 33071; + case TextureMap::MIRRORED_REPEAT: + return 33648; + case TextureMap::REPEAT: + return 10497; + default: + return -1; + } +} + +// Checks |att| metadata entry in |mesh| with key "attribute_name" and returns +// entry value if it begins with "_FEATURE_ID_", or an empty string otherwise. +std::string GetFeatureIdAttributeName(const PointAttribute &att, + const Mesh &mesh) { + const auto *const metadata = + mesh.GetAttributeMetadataByAttributeId(att.unique_id()); + if (metadata) { + std::string attribute_name; + if (metadata->GetEntryString("attribute_name", &attribute_name)) { + constexpr char kPrefix[] = "_FEATURE_ID_"; + if (attribute_name.rfind(kPrefix) == 0) { + return attribute_name; + } + } + } + return std::string(); +} + +// Struct to hold glTF Scene data. +struct GltfScene { + std::vector node_indices; +}; + +// Struct to hold glTF Node data. +struct GltfNode { + GltfNode() + : mesh_index(-1), + skin_index(-1), + light_index(-1), + instance_array_index(-1), + root_node(false) {} + + std::string name; + std::vector childern_indices; + int mesh_index; + int skin_index; + int light_index; + int instance_array_index; + bool root_node; + TrsMatrix trs_matrix; +}; + +// Struct to hold image data. +struct GltfImage { + std::string image_name; + const Texture *texture; + std::unique_ptr owned_texture; + int num_components = 0; + int buffer_view = -1; + std::string mime_type; +}; + +// Struct to hold texture filtering options. The members are based on glTF 2.0 +// samplers. For more information see: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#samplers +struct TextureSampler { + TextureSampler(TextureMap::FilterType min, TextureMap::FilterType mag, + TextureMap::WrappingMode mode) + : min_filter(min), mag_filter(mag), wrapping_mode(mode) {} + + bool operator==(const TextureSampler &other) const { + if (min_filter != other.min_filter) { + return false; + } + if (mag_filter != other.mag_filter) { + return false; + } + return wrapping_mode.s == other.wrapping_mode.s && + wrapping_mode.t == other.wrapping_mode.t; + } + + TextureMap::FilterType min_filter = TextureMap::UNSPECIFIED; + TextureMap::FilterType mag_filter = TextureMap::UNSPECIFIED; + TextureMap::WrappingMode wrapping_mode = {TextureMap::CLAMP_TO_EDGE, + TextureMap::CLAMP_TO_EDGE}; +}; + +// Struct to hold texture data. Multiple textures can reference the same image. +struct GltfTexture { + GltfTexture(int image, int sampler) + : image_index(image), sampler_index(sampler) {} + bool operator==(const GltfTexture &other) const { + return image_index == other.image_index && + sampler_index == other.sampler_index; + } + int image_index; + int sampler_index; +}; + +// Struct to hold glTF Accessor data. +struct GltfAccessor { + GltfAccessor() + : buffer_view_index(-1), + byte_stride(0), + component_type(-1), + normalized(false) {} + + int buffer_view_index; + int byte_stride; + int component_type; + int64_t count; + std::vector max; + std::vector min; + std::string type; + bool normalized; +}; + +// Struct to hold glTF BufferView data. Currently there is only one Buffer, so +// there is no need to store a buffer index. +struct GltfBufferView { + int64_t buffer_byte_offset = -1; + int64_t byte_length = 0; + int target = 0; +}; + +// Struct to hold information about a Draco compressed mesh. +struct GltfDracoCompressedMesh { + int buffer_view_index = -1; + std::map attributes; +}; + +// Struct to hold glTF Primitive data. +struct GltfPrimitive { + GltfPrimitive() : indices(-1), mode(4), material(0) {} + + int indices; + int mode; + int material; + std::vector material_variants_mappings; + std::vector mesh_features; + std::map attributes; + GltfDracoCompressedMesh compressed_mesh_info; +}; + +struct GltfMesh { + std::string name; + std::vector primitives; +}; + +// Class to hold and output glTF data. +class GltfAsset { + public: + // glTF value types and values. + enum ComponentType { + BYTE = 5120, + UNSIGNED_BYTE = 5121, + SHORT = 5122, + UNSIGNED_SHORT = 5123, + UNSIGNED_INT = 5125, + FLOAT = 5126 + }; + // Return the size of the component based on |max_value|. + static int UnsignedIntComponentSize(unsigned int max_value); + + // Return component type based on |max_value|. + static ComponentType UnsignedIntComponentType(unsigned int max_value); + + GltfAsset(); + + std::string generator() const { return generator_; } + std::string version() const { return version_; } + std::string buffer_name() const { return buffer_name_; } + void buffer_name(const std::string &name) { buffer_name_ = name; } + const EncoderBuffer *Buffer() const { return &buffer_; } + + // Convert a Draco Mesh to glTF data. + bool AddDracoMesh(const Mesh &mesh); + + // Convert a Draco Scene to glTF data. + Status AddScene(const Scene &scene); + + // Copy the glTF data to |buf_out|. + Status Output(EncoderBuffer *buf_out); + + // Return the output image referenced by |index|. + const GltfImage *GetImage(int index) const; + + // Return the number of images added to the GltfAsset. + int NumImages() const { return images_.size(); } + + const std::string &image_name(int i) const { return images_[i].image_name; } + + void set_add_images_to_buffer(bool flag) { add_images_to_buffer_ = flag; } + bool add_images_to_buffer() const { return add_images_to_buffer_; } + void set_output_type(GltfEncoder::OutputType type) { output_type_ = type; } + GltfEncoder::OutputType output_type() const { return output_type_; } + void set_json_output_mode(JsonWriter::Mode mode) { gltf_json_.SetMode(mode); } + + private: + // Pad |buffer_| to 4 byte boundary. + bool PadBuffer(); + + // Returns the index of the scene that was added. -1 on error. + int AddScene(); + + // Add a glTF attribute index to |draco_extension|. + void AddAttributeToDracoExtension( + const Mesh &mesh, GeometryAttribute::Type type, int index, + const std::string &name, GltfDracoCompressedMesh *compressed_mesh_info); + + // Compresses |mesh| using Draco. On success returns the buffer_view in + // |primitive| and number of encoded points and faces. + Status CompressMeshWithDraco(const Mesh &mesh, + const Eigen::Matrix4d &transform, + GltfPrimitive *primitive, + int64_t *num_encoded_points, + int64_t *num_encoded_faces); + + // Adds a Draco mesh associated with a material id and material variants. + bool AddDracoMesh(const Mesh &mesh, int material_id, + const std::vector + &material_variants_mappings, + const Eigen::Matrix4d &transform); + + // Add the Draco mesh indices to the glTF data. |num_encoded_faces| is the + // number of faces encoded in |mesh|, which can be different than + // mesh.numfaces(). Returns the index of the accessor that was added. -1 on + // error. + int AddDracoIndices(const Mesh &mesh, int64_t num_encoded_faces); + + // Add the Draco mesh positions attribute to the glTF data. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoPositions(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh normals attribute to the glTF data. |num_encoded_points| + // is the number of points encoded in |mesh|, which can be different than + // mesh.num_points(). Returns the index of + // the accessor that was added. -1 on error. + int AddDracoNormals(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh vertex color attribute to the glTF data. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoColors(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh texture attribute to the glTF data. |tex_coord_index| is + // the index into the texture coordinates added to |mesh|. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoTexture(const Mesh &mesh, int tex_coord_index, + int num_encoded_points); + + // Add the Draco mesh tangent attribute to the glTF data. The Draco mesh + // tangents only contains the x, y, and z components and glTF needs the + // x, y, z, and w components for glTF mesh tangents. Note this is not true + // for tangents of glTF morph targets. This function will add the w component + // to the glTF tangents. |num_encoded_points| is the + // number of points encoded in |mesh|, which can be different than + // mesh.num_points(). Returns the index of the accessor that was added. + // -1 on error. + // Note: Tangents are not added if the attribute contains "auto_generated" + // metadata. See go/tangents_and_draco_simplifier for more details. + int AddDracoTangents(const Mesh &mesh, int num_encoded_points); + + int AddDracoJoints(const Mesh &mesh, int num_encoded_points); + int AddDracoWeights(const Mesh &mesh, int num_encoded_points); + std::vector> AddDracoGenerics( + const Mesh &mesh, int num_encoded_points); + + // Iterate through the materials that are associated with |mesh| and add them + // to the asset. Returns true if |mesh| does not contain any materials or all + // the materials are supported. Returns false if |mesh| contains materials + // that are not supported. + bool AddMaterials(const Mesh &mesh); + + // Checks whether a given Draco |attribute| has data of expected |data_type| + // and whether the data has one of expected |num_components|. Returns true + // when the |attribute| meets expectations, false otherwise. + static bool CheckDracoAttribute(const PointAttribute *attribute, + const std::set &data_types, + const std::set &num_components); + + // Returns the name of |texture|. If |texture|'s name is empty then it will + // generate a name using |texture_index| and |suffix|. If it cannot generate a + // name then it will return an empty string. + std::string GetTextureName(const Texture &texture, int texture_index, + const std::string &suffix) const; + + // Adds a new glTF image to the asset and returns its index. |owned_texture| + // is an optional argument that can be used when the added image is not + // contained in the encoded MaterialLibrary (e.g. for images that are locally + // modified before they are encoded to disk). The image file name is generated + // by combining |image_stem| and image mime type contained in the |texture|. + StatusOr AddImage(const std::string &image_stem, const Texture *texture, + int num_components); + StatusOr AddImage(const std::string &image_stem, const Texture *texture, + std::unique_ptr owned_texture, + int num_components); + + // Saves an image with a given |image_index| into a buffer. + Status SaveImageToBuffer(int image_index); + + // Adds |sampler| to vector of samplers and returns the index. If |sampler| is + // equal to default values then |sampler| is not added to the vector and + // returns -1. + StatusOr AddTextureSampler(const TextureSampler &sampler); + + // Adds a Draco SceneNode, referenced by |scene_node_index|, to the glTF data. + Status AddSceneNode(const Scene &scene, SceneNodeIndex scene_node_index); + + // Iterate through the materials that are associated with |scene| and add them + // to the asset. Returns true if |scene| does not contain any materials or all + // the materials are supported. Returns false if |scene| contains materials + // that are not supported. + bool AddMaterials(const Scene &scene); + + // Iterate through the animations that are associated with |scene| and add + // them to the asset. Returns OkStatus() if |scene| does not contain any + // animations. + Status AddAnimations(const Scene &scene); + + // Converts the data associated with |node_animation_data| and adds that to + // the encoder as an accessor. + StatusOr AddNodeAnimationData( + const NodeAnimationData &node_animation_data); + + // Iterate through the skins that are associated with |scene| and add + // them to the asset. Returns OkStatus() if |scene| does not contain any + // skins. + Status AddSkins(const Scene &scene); + + // Iterate through the lights that are associated with |scene| and add them to + // the asset. Returns OkStatus() if |scene| does not contain any lights. + Status AddLights(const Scene &scene); + + // Iterate through materials variants names that are associated with |scene| + // and add them to the asset. Returns OkStatus() if |scene| does not contain + // any materials variants. + Status AddMaterialsVariantsNames(const Scene &scene); + + // Iterate through the mesh group instance arrays that are associated with + // |scene| and add them to the asset. Returns OkStatus() if |scene| does not + // contain any mesh group instance arrays. + Status AddInstanceArrays(const Scene &scene); + + // Adds structural metadata from |geometry| to the asset, if any. + template + void AddStructuralMetadata(const GeometryT &geometry); + + // Adds float |data| representing |num_components|-length vectors to the + // encoder as accessor and return the new accessor index. + StatusOr AddData(const std::vector &data, int num_components); + + // Adds property table |data| as buffer view and returns buffer view index. + StatusOr AddBufferView(const PropertyTable::Property::Data &data); + + bool EncodeAssetProperty(EncoderBuffer *buf_out); + bool EncodeScenesProperty(EncoderBuffer *buf_out); + bool EncodeInitialSceneProperty(EncoderBuffer *buf_out); + bool EncodeNodesProperty(EncoderBuffer *buf_out); + Status EncodeMeshesProperty(EncoderBuffer *buf_out); + Status EncodePrimitiveExtensionsProperty(const GltfPrimitive &primitive, + EncoderBuffer *buf_out); + Status EncodeMaterials(EncoderBuffer *buf_out); + + // Encodes a color material. |red|, |green|, |blue|, |alpha|, and + // |metallic_factor| are values in the range of 0.0 - 1.0. + void EncodeColorMaterial(float red, float green, float blue, float alpha, + float metallic_factor); + Status EncodeDefaultMaterial(EncoderBuffer *buf_out); + + // Encodes a texture map. |object_name| is the name of the texture map. + // |image_index| is the index into the texture image array. |tex_coord_index| + // is the index into the texture coordinates. |texture_map| is a reference to + // the texture map that is going to be encoded. + Status EncodeTextureMap(const std::string &object_name, int image_index, + int tex_coord_index, const Material &material, + const TextureMap &texture_map); + + // Encodes a texture map similar to the method above. When the |object_name| + // is "texture" and |channels| is not empty, then the |channels| is encoded + // into the "channels" property as required by the "texture" object of the + // EXT_mesh_features extension. + Status EncodeTextureMap(const std::string &object_name, int image_index, + int tex_coord_index, const Material &material, + const TextureMap &texture_map, + const std::vector &channels); + Status EncodeMaterialsProperty(EncoderBuffer *buf_out); + + void EncodeMaterialUnlitExtension(const Material &material); + Status EncodeMaterialSheenExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialTransmissionExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialClearcoatExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialVolumeExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialIorExtension(const Material &material, + const Material &defaults); + Status EncodeMaterialSpecularExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeTexture(const std::string &name, const std::string &stem_suffix, + TextureMap::Type type, int num_components, + const Material &material, int material_index); + Status EncodeAnimationsProperty(EncoderBuffer *buf_out); + Status EncodeSkinsProperty(EncoderBuffer *buf_out); + Status EncodeTopLevelExtensionsProperty(EncoderBuffer *buf_out); + Status EncodeLightsProperty(EncoderBuffer *buf_out); + Status EncodeMaterialsVariantsNamesProperty(EncoderBuffer *buf_out); + Status EncodeStructuralMetadataProperty(EncoderBuffer *buf_out); + bool EncodeAccessorsProperty(EncoderBuffer *buf_out); + bool EncodeBufferViewsProperty(EncoderBuffer *buf_out); + bool EncodeBuffersProperty(EncoderBuffer *buf_out); + Status EncodeExtensionsProperties(EncoderBuffer *buf_out); + + // Encodes a draco::VectorNX as a glTF array. + template + void EncodeVectorArray(const std::string &array_name, T vec) { + gltf_json_.BeginArray(array_name); + for (int i = 0; i < T::dimension; ++i) { + gltf_json_.OutputValue(vec[i]); + } + gltf_json_.EndArray(); + } + + // Add a mesh Draco attribute |att| that is comprised of floats to the glTF + // data. Returns the index accessor added to the glTF data. Returns -1 on + // error. + template + int AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, bool compress) { + const int num_components = att.num_components(); + switch (num_components) { + case 1: + return AddAttribute<1, att_data_t>(att, num_points, num_encoded_points, + "SCALAR", compress); + break; + case 2: + return AddAttribute<2, att_data_t>(att, num_points, num_encoded_points, + "VEC2", compress); + break; + case 3: + return AddAttribute<3, att_data_t>(att, num_points, num_encoded_points, + "VEC3", compress); + break; + case 4: + return AddAttribute<4, att_data_t>(att, num_points, num_encoded_points, + "VEC4", compress); + break; + default: + break; + } + return -1; + } + + // Template method only has specialized implementations for known glTF types. + template + ComponentType GetComponentType() const = delete; + + template + int AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, const std::string &type, + bool compress); + + std::string generator_; + std::string version_; + std::vector scenes_; + + // Initial scene to load. + int scene_index_; + + std::vector nodes_; + std::vector accessors_; + std::vector buffer_views_; + std::vector meshes_; + + // Data structure to copy the input meshes materials. + MaterialLibrary material_library_; + + std::vector images_; + std::vector textures_; + + std::unordered_map texture_to_image_index_map_; + + std::string buffer_name_; + EncoderBuffer buffer_; + JsonWriter gltf_json_; + + // Keeps track if the glTF mesh has been added. + std::map mesh_group_index_to_gltf_mesh_; + std::map> mesh_index_to_gltf_mesh_primitive_; + IndexTypeVector base_mesh_transforms_; + + struct EncoderAnimation { + std::string name; + std::vector> samplers; + std::vector> channels; + }; + std::vector> animations_; + + struct EncoderSkin { + EncoderSkin() : inverse_bind_matrices_index(-1), skeleton_index(-1) {} + int inverse_bind_matrices_index; + std::vector joints; + int skeleton_index; + }; + + // Instance array is represented by its attribute accessors. + struct EncoderInstanceArray { + EncoderInstanceArray() : translation(-1), rotation(-1), scale(-1) {} + int translation; + int rotation; + int scale; + }; + + std::vector> skins_; + std::vector> lights_; + std::vector materials_variants_names_; + std::vector instance_arrays_; + PropertyTable::Schema property_table_schema_; + std::vector property_tables_; + + // Indicates whether Draco compression is used for any of the asset meshes. + bool draco_compression_used_; + + // Indicates whether mesh features are used. + bool mesh_features_used_; + + // Counter for naming mesh feature textures. + int mesh_features_texture_index_; + + // If set GltfAsset will add the images to |buffer_| instead of writing the + // images to separate files. + bool add_images_to_buffer_; + + // Used to hold the extensions used and required by the glTF asset. + std::set extensions_used_; + std::set extensions_required_; + + std::vector texture_samplers_; + + GltfEncoder::OutputType output_type_; + + // Temporary storage for meshes created during the runtime of the GltfEncoder. + // We need to store them here to ensure their content doesn't get deleted + // before it is used by the encoder. + std::vector> local_meshes_; +}; + +int GltfAsset::UnsignedIntComponentSize(unsigned int max_value) { + // According to GLTF 2.0 spec, 0xff (and 0xffff respectively) are reserved for + // the primitive restart symbol. + if (max_value < 0xff) { + return 1; + } else if (max_value < 0xffff) { + return 2; + } + return 4; +} + +GltfAsset::ComponentType GltfAsset::UnsignedIntComponentType( + unsigned int max_value) { + // According to GLTF 2.0 spec, 0xff (and 0xffff respectively) are reserved for + // the primitive restart symbol. + if (max_value < 0xff) { + return UNSIGNED_BYTE; + } else if (max_value < 0xffff) { + return UNSIGNED_SHORT; + } + return UNSIGNED_INT; +} + +GltfAsset::GltfAsset() + : generator_("draco_decoder"), + version_("2.0"), + scene_index_(-1), + buffer_name_("buffer0.bin"), + draco_compression_used_(false), + mesh_features_used_(false), + mesh_features_texture_index_(0), + add_images_to_buffer_(false), + output_type_(GltfEncoder::COMPACT) {} + +bool GltfAsset::AddDracoMesh(const Mesh &mesh) { + const int scene_index = AddScene(); + if (scene_index < 0) { + return false; + } + if (!AddMaterials(mesh)) { + return false; + } + + GltfMesh gltf_mesh; + meshes_.push_back(gltf_mesh); + + AddStructuralMetadata(mesh); + + const int32_t material_att_id = + mesh.GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (material_att_id == -1) { + if (!AddDracoMesh(mesh, 0, {}, Eigen::Matrix4d::Identity())) { + return false; + } + } else { + const auto mat_att = mesh.GetNamedAttribute(GeometryAttribute::MATERIAL); + + // Split mesh using the material attribute. + MeshSplitter splitter; + auto split_maybe = splitter.SplitMesh(mesh, material_att_id); + if (!split_maybe.ok()) { + return false; + } + auto split_meshes = std::move(split_maybe).value(); + for (int i = 0; i < split_meshes.size(); ++i) { + if (split_meshes[i] == nullptr) { + continue; // Empty mesh. Ignore. + } + uint32_t mat_index = 0; + mat_att->GetValue(AttributeValueIndex(i), &mat_index); + + // Copy over mesh features for a given material index. + Mesh::CopyMeshFeaturesForMaterial(mesh, split_meshes[i].get(), mat_index); + + // Move the split mesh to a temporary storage of the GltfAsset. This will + // ensure the mesh will stay alive as long the asset needs it. We have to + // do this because the split mesh may contain mesh features data that are + // used later in the encoding process. + local_meshes_.push_back(std::move(split_meshes[i])); + + // The material index in the glTF file corresponds to the index of the + // split mesh. + if (!AddDracoMesh(*(local_meshes_.back().get()), mat_index, {}, + Eigen::Matrix4d::Identity())) { + return false; + } + } + } + + // Currently output only one mesh. + GltfNode mesh_node; + mesh_node.mesh_index = 0; + nodes_.push_back(mesh_node); + nodes_.back().root_node = true; + return true; +} + +int GltfAsset::AddScene() { + GltfScene scene; + scenes_.push_back(scene); + const int scene_index = static_cast(scenes_.size()) - 1; + + if (scene_index_ == -1) { + scene_index_ = scene_index; + } + return scene_index; +} + +Status GltfAsset::Output(EncoderBuffer *buf_out) { + gltf_json_.BeginObject(); + if (!EncodeAssetProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding asset."); + } + if (!EncodeScenesProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding scenes."); + } + if (!EncodeInitialSceneProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding initial scene."); + } + if (!EncodeNodesProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding nodes."); + } + DRACO_RETURN_IF_ERROR(EncodeMeshesProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeMaterials(buf_out)); + if (!EncodeAccessorsProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding accessors."); + } + DRACO_RETURN_IF_ERROR(EncodeAnimationsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeSkinsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeTopLevelExtensionsProperty(buf_out)); + if (!EncodeBufferViewsProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding buffer views."); + } + if (!EncodeBuffersProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding buffers."); + } + DRACO_RETURN_IF_ERROR(EncodeExtensionsProperties(buf_out)); + gltf_json_.EndObject(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Failed encoding json data."); + } + if (!buf_out->Encode("\n", 1)) { + return Status(Status::DRACO_ERROR, "Failed encoding json data."); + } + return OkStatus(); +} + +const GltfImage *GltfAsset::GetImage(int index) const { + if (index < 0 || index >= images_.size()) { + return nullptr; + } + return &images_[index]; +} + +bool GltfAsset::PadBuffer() { + if (buffer_.size() % 4 != 0) { + const int pad_bytes = 4 - buffer_.size() % 4; + const int pad_data = 0; + if (!buffer_.Encode(&pad_data, pad_bytes)) { + return false; + } + } + return true; +} + +void GltfAsset::AddAttributeToDracoExtension( + const Mesh &mesh, GeometryAttribute::Type type, int index, + const std::string &name, GltfDracoCompressedMesh *compressed_mesh_info) { + if (mesh.IsCompressionEnabled()) { + const PointAttribute *const att = mesh.GetNamedAttribute(type, index); + if (att) { + compressed_mesh_info->attributes.insert( + std::pair(name, att->unique_id())); + } + } +} + +Status GltfAsset::CompressMeshWithDraco(const Mesh &mesh, + const Eigen::Matrix4d &transform, + GltfPrimitive *primitive, + int64_t *num_encoded_points, + int64_t *num_encoded_faces) { + // Check that geometry comression options are valid. + DracoCompressionOptions compression_options = mesh.GetCompressionOptions(); + DRACO_RETURN_IF_ERROR(compression_options.Check()); + + // Make a copy of the mesh. It will be modified and compressed. + std::unique_ptr mesh_copy(new Mesh()); + mesh_copy->Copy(mesh); + + // Delete auto-generated tangents. + if (MeshUtils::HasAutoGeneratedTangents(*mesh_copy)) { + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TANGENT) { + while (mesh_copy->GetNamedAttribute(GeometryAttribute::TANGENT)) { + mesh_copy->DeleteAttribute( + mesh_copy->GetNamedAttributeId(GeometryAttribute::TANGENT)); + } + break; + } + } + } + + // Create Draco encoder. + EncoderBuffer buffer; + ExpertEncoder encoder(*mesh_copy); + encoder.SetTrackEncodedProperties(true); + + // Convert compression level to speed (that 0 = slowest, 10 = fastest). + const int speed = 10 - compression_options.compression_level; + encoder.SetSpeedOptions(speed, speed); + + // Configure attribute quantization. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + const PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::POSITION && + !compression_options.quantization_position + .AreQuantizationBitsDefined()) { + // Desired spacing in the "global" coordinate system. + const float global_spacing = + compression_options.quantization_position.spacing(); + + // Note: Ideally we would transform the whole mesh before encoding and + // apply the original global spacing on the transformed mesh. But neither + // KHR_draco_mesh_compression, nor Draco bitstream support post-decoding + // transformations so we have to modify the grid settings here. + + // Transform this spacing to the local coordinate system of the base mesh. + // We will get the largest scale factor from the transformation matrix and + // use it to adjust the grid spacing. + const Vector3f scale_vec(transform.col(0).norm(), transform.col(1).norm(), + transform.col(2).norm()); + + const float max_scale = scale_vec.MaxCoeff(); + + // Spacing is inverse to the scale. The larger the scale, the smaller the + // spacing must be. + const float local_spacing = global_spacing / max_scale; + + // Update the compression options of the processed mesh. + compression_options.quantization_position.SetGrid(local_spacing); + } else { + int num_quantization_bits = -1; + switch (att->attribute_type()) { + case GeometryAttribute::POSITION: + num_quantization_bits = + compression_options.quantization_position.quantization_bits(); + break; + case GeometryAttribute::NORMAL: + num_quantization_bits = compression_options.quantization_bits_normal; + break; + case GeometryAttribute::TEX_COORD: + num_quantization_bits = + compression_options.quantization_bits_tex_coord; + break; + case GeometryAttribute::TANGENT: + num_quantization_bits = compression_options.quantization_bits_tangent; + break; + case GeometryAttribute::WEIGHTS: + num_quantization_bits = compression_options.quantization_bits_weight; + break; + case GeometryAttribute::GENERIC: + if (GetFeatureIdAttributeName(*att, *mesh_copy).empty()) { + num_quantization_bits = + compression_options.quantization_bits_generic; + } else { + // Quantization is explicitly disabled for feature ID attributes. + encoder.SetAttributeQuantization(i, -1); + } + break; + default: + break; + } + if (num_quantization_bits > 0) { + encoder.SetAttributeQuantization(i, num_quantization_bits); + } + } + } + + // Flip UV values as required by glTF Draco and non-Draco files. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TEX_COORD) { + if (!MeshUtils::FlipTextureUvValues(false, true, att)) { + return Status(Status::DRACO_ERROR, "Could not flip texture UV values."); + } + } + } + + // Change tangents, joints, and weights attribute types to generic. The + // original mesh's attribute type is unchanged and the mapping of the glTF + // attribute type to Draco compressed attribute id is written to the output + // glTF file. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TANGENT || + att->attribute_type() == GeometryAttribute::JOINTS || + att->attribute_type() == GeometryAttribute::WEIGHTS) { + att->set_attribute_type(GeometryAttribute::GENERIC); + } + } + + // |compression_options| may have been modified and we need to update them + // before we start the encoding. + mesh_copy->SetCompressionOptions(compression_options); + DRACO_RETURN_IF_ERROR(encoder.EncodeToBuffer(&buffer)); + *num_encoded_points = encoder.num_encoded_points(); + *num_encoded_faces = encoder.num_encoded_faces(); + const size_t buffer_start_offset = buffer_.size(); + if (!buffer_.Encode(buffer.data(), buffer.size())) { + return Status(Status::DRACO_ERROR, "Could not copy Draco compressed data."); + } + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, "Could not pad glTF buffer."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + primitive->compressed_mesh_info.buffer_view_index = + static_cast(buffer_views_.size() - 1); + return OkStatus(); +} + +bool CheckAndGetTexCoordAttributeOrder(const Mesh &mesh, + std::vector *tex_coord_order) { + // We will only consider at most two texture coordinate attributes. + *tex_coord_order = {0, 1}; + const int num_attributes = + std::min(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + + // Collect texture coordinate attribute names from metadata. + std::vector names(num_attributes, ""); + for (int i = 0; i < num_attributes; i++) { + const auto metadata = mesh.GetAttributeMetadataByAttributeId( + mesh.GetNamedAttributeId(GeometryAttribute::TEX_COORD, i)); + std::string attribute_name; + if (metadata != nullptr) { + metadata->GetEntryString("attribute_name", &attribute_name); + names[i] = attribute_name; + } + } + + // Attribute names may be absent. + if (num_attributes == 0 || + std::all_of(names.begin(), names.end(), + [](const std::string &name) { return name.empty(); })) { + return true; + } + + // Attribute names must be unique. + const std::unordered_set unique_names(names.begin(), + names.end()); + if (unique_names.size() != num_attributes) { + return false; + } + + // Attribute names must be valid. + if (std::any_of(names.begin(), names.end(), [](const std::string &name) { + return name != "TEXCOORD_0" && name != "TEXCOORD_1"; + })) { + return false; + } + + // Populate texture coordinate order index based on attribute names. + if (names[0] == "TEXCOORD_1") { + *tex_coord_order = {1, 0}; + } + return true; +} + +bool GltfAsset::AddDracoMesh( + const Mesh &mesh, int material_id, + const std::vector + &material_variants_mappings, + const Eigen::Matrix4d &transform) { + GltfPrimitive primitive; + int64_t num_encoded_points = mesh.num_points(); + int64_t num_encoded_faces = mesh.num_faces(); + if (num_encoded_faces > 0 && mesh.IsCompressionEnabled()) { + const Status status = CompressMeshWithDraco( + mesh, transform, &primitive, &num_encoded_points, &num_encoded_faces); + if (!status.ok()) { + return false; + } + draco_compression_used_ = true; + } + int indices_index = -1; + if (num_encoded_faces > 0) { + indices_index = AddDracoIndices(mesh, num_encoded_faces); + if (indices_index < 0) { + return false; + } + } + const int position_index = AddDracoPositions(mesh, num_encoded_points); + if (position_index < 0) { + return false; + } + // Check texture coordinate attributes and get the desired encoding order. + std::vector tex_coord_order; + if (!CheckAndGetTexCoordAttributeOrder(mesh, &tex_coord_order)) { + return false; + } + const int normals_accessor_index = AddDracoNormals(mesh, num_encoded_points); + const int colors_accessor_index = AddDracoColors(mesh, num_encoded_points); + const int texture0_accessor_index = + AddDracoTexture(mesh, tex_coord_order[0], num_encoded_points); + const int texture1_accessor_index = + AddDracoTexture(mesh, tex_coord_order[1], num_encoded_points); + const int tangent_accessor_index = AddDracoTangents(mesh, num_encoded_points); + const int joints_accessor_index = AddDracoJoints(mesh, num_encoded_points); + const int weights_accessor_index = AddDracoWeights(mesh, num_encoded_points); + const std::vector> generics_accessors = + AddDracoGenerics(mesh, num_encoded_points); + + if (num_encoded_faces == 0) { + primitive.mode = 0; // POINTS mode. + } + primitive.material = material_id; + primitive.material_variants_mappings = material_variants_mappings; + primitive.mesh_features.reserve(mesh.NumMeshFeatures()); + for (MeshFeaturesIndex i(0); i < mesh.NumMeshFeatures(); ++i) { + primitive.mesh_features.push_back(&mesh.GetMeshFeatures(i)); + } + primitive.indices = indices_index; + primitive.attributes.insert( + std::pair("POSITION", position_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::POSITION, 0, "POSITION", + &primitive.compressed_mesh_info); + if (normals_accessor_index > 0) { + primitive.attributes.insert( + std::pair("NORMAL", normals_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::NORMAL, 0, "NORMAL", + &primitive.compressed_mesh_info); + } + if (colors_accessor_index > 0) { + primitive.attributes.insert( + std::pair("COLOR_0", colors_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::COLOR, 0, "COLOR_0", + &primitive.compressed_mesh_info); + } + if (texture0_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TEXCOORD_0", texture0_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TEX_COORD, 0, + "TEXCOORD_0", &primitive.compressed_mesh_info); + } + if (texture1_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TEXCOORD_1", texture1_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TEX_COORD, 1, + "TEXCOORD_1", &primitive.compressed_mesh_info); + } + if (tangent_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TANGENT", tangent_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TANGENT, 0, "TANGENT", + &primitive.compressed_mesh_info); + } + if (joints_accessor_index > 0) { + primitive.attributes.insert( + std::pair("JOINTS_0", joints_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::JOINTS, 0, "JOINTS_0", + &primitive.compressed_mesh_info); + } + if (weights_accessor_index > 0) { + primitive.attributes.insert( + std::pair("WEIGHTS_0", weights_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::WEIGHTS, 0, + "WEIGHTS_0", &primitive.compressed_mesh_info); + } + for (int att_index = 0; att_index < generics_accessors.size(); ++att_index) { + const std::string &attribute_name = generics_accessors[att_index].first; + if (!attribute_name.empty()) { + primitive.attributes.insert(generics_accessors[att_index]); + AddAttributeToDracoExtension(mesh, GeometryAttribute::GENERIC, att_index, + attribute_name, + &primitive.compressed_mesh_info); + } + } + + meshes_.back().primitives.push_back(primitive); + return true; +} + +int GltfAsset::AddDracoIndices(const Mesh &mesh, int64_t num_encoded_faces) { + // Get the min and max value for the indices. + uint32_t min_index = 0xffffffff; + uint32_t max_index = 0; + for (FaceIndex i(0); i < mesh.num_faces(); ++i) { + const auto &f = mesh.face(i); + + for (int j = 0; j < 3; ++j) { + if (f[j] < min_index) { + min_index = f[j].value(); + } + if (f[j] > max_index) { + max_index = f[j].value(); + } + } + } + + const int component_size = GltfAsset::UnsignedIntComponentSize(max_index); + + GltfAccessor accessor; + if (!mesh.IsCompressionEnabled()) { + const size_t buffer_start_offset = buffer_.size(); + for (FaceIndex i(0); i < mesh.num_faces(); ++i) { + const auto &f = mesh.face(i); + for (int j = 0; j < 3; ++j) { + int index = f[j].value(); + if (!buffer_.Encode(&index, component_size)) { + return -1; + } + } + } + + if (!PadBuffer()) { + return -1; + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + } + + accessor.component_type = UnsignedIntComponentType(max_index); + accessor.count = num_encoded_faces * 3; + if (output_type_ == GltfEncoder::VERBOSE) { + accessor.max.push_back(GltfValue(max_index)); + accessor.min.push_back(GltfValue(min_index)); + } + accessor.type = "SCALAR"; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +int GltfAsset::AddDracoPositions(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::POSITION); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoNormals(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::NORMAL); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoColors(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::COLOR); + // TODO(b/200302561): Add support for DT_UINT16 with COLOR. + if (!CheckDracoAttribute(att, {DT_UINT8, DT_FLOAT32}, {3, 4})) { + return -1; + } + if (att->data_type() == DT_FLOAT32) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoTexture(const Mesh &mesh, int tex_coord_index, + int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::TEX_COORD, tex_coord_index); + // TODO(b/200303080): Add support for DT_UINT8 and DT_UINT16 with TEX_COORD. + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {2})) { + return -1; + } + + // glTF stores texture coordinates flipped on the horizontal axis compared to + // how Draco stores texture coordinates. + GeometryAttribute ga; + ga.Init(GeometryAttribute::TEX_COORD, nullptr, 2, att->data_type(), false, + DataTypeLength(att->data_type()) * 2, 0); + PointAttribute ta(ga); + ta.SetIdentityMapping(); + ta.Reset(mesh.num_points()); + + std::array value; + for (PointIndex v(0); v < mesh.num_points(); ++v) { + if (!att->GetValue(att->mapped_index(v), &value)) { + return -1; + } + + // Draco texture v component needs to be flipped. + Vector2f texture_coord(value[0], 1.0 - value[1]); + ta.SetAttributeValue(AttributeValueIndex(v.value()), texture_coord.data()); + } + return AddAttribute(ta, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoTangents(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::TANGENT); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3, 4})) { + return -1; + } + if (MeshUtils::HasAutoGeneratedTangents(mesh)) { + // Ignore auto-generated tangents. See go/tangents_and_draco_simplifier. + return -1; + } + + if (att->num_components() == 4) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + + // glTF mesh needs the w component. + GeometryAttribute ga; + ga.Init(GeometryAttribute::TANGENT, nullptr, 4, DT_FLOAT32, false, + DataTypeLength(DT_FLOAT32) * 4, 0); + PointAttribute ta(ga); + ta.SetIdentityMapping(); + ta.Reset(mesh.num_points()); + + std::array value; + for (PointIndex v(0); v < mesh.num_points(); ++v) { + if (!att->GetValue(att->mapped_index(v), &value)) { + return -1; + } + + // Draco tangent w component is always 1.0. + Vector4f tangent(value[0], value[1], value[2], 1.0); + ta.SetAttributeValue(AttributeValueIndex(v.value()), tangent.data()); + } + return AddAttribute(ta, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoJoints(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::JOINTS); + if (!CheckDracoAttribute(att, {DT_UINT8, DT_UINT16}, {4})) { + return -1; + } + if (att->data_type() == DT_UINT16) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoWeights(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::WEIGHTS); + // TODO(b/200303026): Add support for DT_UINT8 and DT_UINT16 with WEIGHTS. + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {4})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +// Adds generic attributes that have metadata describing the attribute name. +// This allows for export of application-specific attributes and feature ID +// attributes defined in glTF extension EXT_mesh_features. Returns a vector of +// attribute-name, accessor pairs for each valid attribute. The length of the +// vector is equal to the number of generic attributes. Vector entries +// corresponding to unsupported attributes (e.g., with no metadata) contain +// empty attribute names. +std::vector> GltfAsset::AddDracoGenerics( + const Mesh &mesh, int num_encoded_points) { + const int num_attributes = + mesh.NumNamedAttributes(GeometryAttribute::GENERIC); + std::vector> attrs(num_attributes); + for (int i = 0; i < num_attributes; ++i) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::GENERIC, i); + auto const *metadata = + mesh.GetAttributeMetadataByAttributeId(att->unique_id()); + if (metadata) { + std::string attr_name; + if (metadata->GetEntryString(GltfEncoder::kDracoMetadataGltfAttributeName, + &attr_name)) { + if (att->data_type() == DT_FLOAT32) { + int accessor = + AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + attrs[i] = {attr_name, accessor}; + } + } else { + // Try to find feature ID attribute name like "_FEATURE_ID_5" then check + // that the attribute stores scalar values of complient data types as + // defined by the EXT_mesh_features glTF extension. + attr_name = GetFeatureIdAttributeName(*att, mesh); + if (!attr_name.empty() && att->num_components() == 1) { + int accessor = -1; + switch (att->data_type()) { + case DT_UINT8: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + case DT_UINT16: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + case DT_FLOAT32: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + default: + continue; + } + attrs[i] = {attr_name, accessor}; + } + } + } + } + return attrs; +} + +bool GltfAsset::AddMaterials(const Mesh &mesh) { + if (mesh.GetMaterialLibrary().NumMaterials() == 0) { + return true; + } + material_library_.Copy(mesh.GetMaterialLibrary()); + return true; +} + +bool GltfAsset::CheckDracoAttribute(const PointAttribute *attribute, + const std::set &data_types, + const std::set &num_components) { + // Attribute must be valid. + if (attribute == nullptr || attribute->size() == 0) { + return false; + } + + // Attribute must have an expected data type. + if (data_types.find(attribute->data_type()) == data_types.end()) { + return false; + } + + // Attribute must have an expected number of components. + if (num_components.find(attribute->num_components()) == + num_components.end()) { + return false; + } + + return true; +} + +StatusOr GltfAsset::AddImage(const std::string &image_stem, + const Texture *texture, int num_components) { + return AddImage(image_stem, texture, nullptr, num_components); +} + +StatusOr GltfAsset::AddImage(const std::string &image_stem, + const Texture *texture, + std::unique_ptr owned_texture, + int num_components) { + const auto it = texture_to_image_index_map_.find(texture); + if (it != texture_to_image_index_map_.end()) { + // We already have an image for the given |texture|. Update its number of + // components if needed. + GltfImage &image = images_[it->second]; + if (image.num_components < num_components) { + image.num_components = num_components; + } + return it->second; + } + std::string extension = TextureUtils::GetTargetExtension(*texture); + if (extension.empty()) { + // Try to get extension from the source file name. + extension = LowercaseFileExtension(texture->source_image().filename()); + } + GltfImage image; + image.image_name = image_stem + "." + extension; + image.texture = texture; + image.owned_texture = std::move(owned_texture); + image.num_components = num_components; + + // Always maintain the mime_type. Used elsewhere to determine image type. + if (extension == "jpg") { + image.mime_type = "image/jpeg"; + } else { + image.mime_type = "image/" + extension; + } + + // For KTX2 with Basis compression, state that its extension is required. + if (extension == "ktx2") { + extensions_used_.insert("KHR_texture_basisu"); + extensions_required_.insert("KHR_texture_basisu"); + } + + // If this is webp, state that its extension is required. + if (extension == "webp") { + extensions_used_.insert("EXT_texture_webp"); + extensions_required_.insert("EXT_texture_webp"); + } + + images_.push_back(std::move(image)); + texture_to_image_index_map_[texture] = images_.size() - 1; + return images_.size() - 1; +} + +Status GltfAsset::SaveImageToBuffer(int image_index) { + GltfImage &image = images_[image_index]; + const Texture *const texture = image.texture; + const int num_components = image.num_components; + std::vector buffer; + DRACO_RETURN_IF_ERROR(WriteTextureToBuffer(*texture, &buffer)); + + // Add the image data to the buffer. + const size_t buffer_start_offset = buffer_.size(); + buffer_.Encode(buffer.data(), buffer.size()); + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, + "Could not pad buffer in SaveImageToBuffer."); + } + + // Add a buffer view pointing to the image data in the buffer. + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + image.buffer_view = buffer_views_.size() - 1; + return OkStatus(); +} + +// TODO(vytyaz): The return type could be int. +StatusOr GltfAsset::AddTextureSampler(const TextureSampler &sampler) { + // If sampler is equal to defaults do not add to vector and return -1. + if (sampler.min_filter == TextureMap::UNSPECIFIED && + sampler.mag_filter == TextureMap::UNSPECIFIED && + sampler.wrapping_mode.s == TextureMap::REPEAT && + sampler.wrapping_mode.t == TextureMap::REPEAT) { + return -1; + } + + const auto &it = + std::find(texture_samplers_.begin(), texture_samplers_.end(), sampler); + if (it != texture_samplers_.end()) { + const int index = std::distance(texture_samplers_.begin(), it); + return index; + } + + texture_samplers_.push_back(sampler); + return texture_samplers_.size() - 1; +} + +Status GltfAsset::AddScene(const Scene &scene) { + const int scene_index = AddScene(); + if (scene_index < 0) { + return Status(Status::DRACO_ERROR, "Error creating a new scene."); + } + if (!AddMaterials(scene)) { + return Status(Status::DRACO_ERROR, "Error adding materials to the scene."); + } + // Initialize base mesh transforms that may be needed when the base meshes are + // compressed with Draco. + base_mesh_transforms_ = SceneUtils::FindLargestBaseMeshTransforms(scene); + for (SceneNodeIndex i(0); i < scene.NumNodes(); ++i) { + DRACO_RETURN_IF_ERROR(AddSceneNode(scene, i)); + } + // There is 1:1 mapping between draco::Scene node indices and |nodes_|. + for (int i = 0; i < scene.NumRootNodes(); ++i) { + nodes_[scene.GetRootNodeIndex(i).value()].root_node = true; + } + DRACO_RETURN_IF_ERROR(AddAnimations(scene)); + DRACO_RETURN_IF_ERROR(AddSkins(scene)); + DRACO_RETURN_IF_ERROR(AddLights(scene)); + DRACO_RETURN_IF_ERROR(AddMaterialsVariantsNames(scene)); + DRACO_RETURN_IF_ERROR(AddInstanceArrays(scene)); + AddStructuralMetadata(scene); + return OkStatus(); +} + +Status GltfAsset::AddSceneNode(const Scene &scene, + SceneNodeIndex scene_node_index) { + const SceneNode *const scene_node = scene.GetNode(scene_node_index); + if (scene_node == nullptr) { + return Status(Status::DRACO_ERROR, "Could not find node in scene."); + } + + GltfNode node; + node.name = scene_node->GetName(); + node.trs_matrix.Copy(scene_node->GetTrsMatrix()); + + for (int i = 0; i < scene_node->NumChildren(); ++i) { + node.childern_indices.push_back(scene_node->Child(i).value()); + } + + const MeshGroupIndex mesh_group_index = scene_node->GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + const auto it = mesh_group_index_to_gltf_mesh_.find(mesh_group_index); + if (it == mesh_group_index_to_gltf_mesh_.end()) { + GltfMesh gltf_mesh; + const MeshGroup *const mesh_group = scene.GetMeshGroup(mesh_group_index); + if (!mesh_group->GetName().empty()) { + gltf_mesh.name = mesh_group->GetName(); + } + meshes_.push_back(gltf_mesh); + + for (int i = 0; i < mesh_group->NumMeshInstances(); ++i) { + const MeshGroup::MeshInstance &instance = + mesh_group->GetMeshInstance(i); + const auto mi_it = + mesh_index_to_gltf_mesh_primitive_.find(instance.mesh_index); + if (mi_it == mesh_index_to_gltf_mesh_primitive_.end()) { + // We have not added the mesh to the scene yet. + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + if (!AddDracoMesh(mesh, instance.material_index, + instance.materials_variants_mappings, + base_mesh_transforms_[instance.mesh_index])) { + return Status(Status::DRACO_ERROR, "Adding a Draco mesh failed."); + } + const int gltf_mesh_index = meshes_.size() - 1; + const int gltf_primitive_index = meshes_.back().primitives.size() - 1; + mesh_index_to_gltf_mesh_primitive_[instance.mesh_index] = + std::make_pair(gltf_mesh_index, gltf_primitive_index); + } else { + // The mesh was already added to the scene. This is a copy instance + // that may have a different material. + const int gltf_mesh_index = mi_it->second.first; + const int gltf_primitive_index = mi_it->second.second; + GltfPrimitive primitive = + meshes_[gltf_mesh_index].primitives[gltf_primitive_index]; + primitive.material = instance.material_index; + primitive.material_variants_mappings = + instance.materials_variants_mappings; + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + primitive.mesh_features.clear(); + primitive.mesh_features.reserve(mesh.NumMeshFeatures()); + for (MeshFeaturesIndex j(0); j < mesh.NumMeshFeatures(); ++j) { + primitive.mesh_features.push_back(&mesh.GetMeshFeatures(j)); + } + meshes_.back().primitives.push_back(primitive); + } + } + mesh_group_index_to_gltf_mesh_[mesh_group_index] = meshes_.size() - 1; + } + node.mesh_index = mesh_group_index_to_gltf_mesh_[mesh_group_index]; + } + node.skin_index = scene_node->GetSkinIndex().value(); + node.light_index = scene_node->GetLightIndex().value(); + node.instance_array_index = scene_node->GetInstanceArrayIndex().value(); + + nodes_.push_back(node); + return OkStatus(); +} + +bool GltfAsset::AddMaterials(const Scene &scene) { + if (scene.GetMaterialLibrary().NumMaterials() == 0) { + return true; + } + material_library_.Copy(scene.GetMaterialLibrary()); + return true; +} + +Status GltfAsset::AddAnimations(const Scene &scene) { + if (scene.NumAnimations() == 0) { + return OkStatus(); + } + // Mapping of the node animation data to the output accessors. The first part + // of the key is the animation index and the second part of the key is the + // node animation data index. + std::map, int> node_animation_data_to_accessor; + + // Mapping of the node animation data to the output accessors. + std::unordered_map + data_to_index_map; + + // First add all the accessors and create a mapping from animation accessors + // to accessors owned by the encoder. + for (AnimationIndex i(0); i < scene.NumAnimations(); ++i) { + const Animation *const animation = scene.GetAnimation(i); + + for (int j = 0; j < animation->NumNodeAnimationData(); ++j) { + const NodeAnimationData *const node_animation_data = + animation->GetNodeAnimationData(j); + + int index = -1; + + NodeAnimationDataHash nadh(node_animation_data); + if (data_to_index_map.find(nadh) == data_to_index_map.end()) { + // The current data is new, add it to the encoder. + DRACO_ASSIGN_OR_RETURN( + index, AddNodeAnimationData(*nadh.GetNodeAnimationData())); + data_to_index_map[nadh] = index; + } else { + index = data_to_index_map[nadh]; + } + + const auto key = std::make_pair(i.value(), j); + node_animation_data_to_accessor[key] = index; + } + } + + // Add all the samplers and channels. + for (AnimationIndex i(0); i < scene.NumAnimations(); ++i) { + const Animation *const animation = scene.GetAnimation(i); + std::unique_ptr new_animation(new EncoderAnimation); + new_animation->name = animation->GetName(); + + for (int j = 0; j < animation->NumSamplers(); ++j) { + const AnimationSampler *const sampler = animation->GetSampler(j); + const auto input_key = std::make_pair(i.value(), sampler->input_index); + const auto input_it = node_animation_data_to_accessor.find(input_key); + if (input_it == node_animation_data_to_accessor.end()) { + return Status(Status::DRACO_ERROR, + "Could not find animation accessor input index."); + } + const auto output_key = std::make_pair(i.value(), sampler->output_index); + const auto output_it = node_animation_data_to_accessor.find(output_key); + if (output_it == node_animation_data_to_accessor.end()) { + return Status(Status::DRACO_ERROR, + "Could not find animation accessor output index."); + } + + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->input_index = input_it->second; + new_sampler->output_index = output_it->second; + + if (output_type_ == GltfEncoder::COMPACT) { + // Remove min/max from output accessor. + accessors_[new_sampler->output_index].min.clear(); + accessors_[new_sampler->output_index].max.clear(); + } + + new_sampler->interpolation_type = sampler->interpolation_type; + + new_animation->samplers.push_back(std::move(new_sampler)); + } + + for (int j = 0; j < animation->NumChannels(); ++j) { + const AnimationChannel *const channel = animation->GetChannel(j); + std::unique_ptr new_channel(new AnimationChannel()); + new_channel->Copy(*channel); + new_animation->channels.push_back(std::move(new_channel)); + } + + animations_.push_back(std::move(new_animation)); + } + return OkStatus(); +} + +StatusOr GltfAsset::AddNodeAnimationData( + const NodeAnimationData &node_animation_data) { + const size_t buffer_start_offset = buffer_.size(); + + const int component_size = node_animation_data.ComponentSize(); + const int num_components = node_animation_data.NumComponents(); + const std::vector *data = node_animation_data.GetData(); + + std::vector min_values; + min_values.resize(num_components); + for (int j = 0; j < num_components; ++j) { + min_values[j] = (*data)[j]; + } + std::vector max_values = min_values; + + for (int i = 0; i < node_animation_data.count(); ++i) { + for (int j = 0; j < num_components; ++j) { + const float value = (*data)[(i * num_components) + j]; + if (value < min_values[j]) { + min_values[j] = value; + } + if (value > max_values[j]) { + max_values[j] = value; + } + + buffer_.Encode(&value, component_size); + } + } + + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, + "AddNodeAnimationData: PadBuffer returned DRACO_ERROR."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + GltfAccessor accessor; + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + accessor.component_type = ComponentType::FLOAT; + accessor.count = node_animation_data.count(); + for (int j = 0; j < num_components; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + accessor.type = node_animation_data.TypeAsString(); + accessor.normalized = node_animation_data.normalized(); + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +Status GltfAsset::AddSkins(const Scene &scene) { + if (scene.NumSkins() == 0) { + return OkStatus(); + } + + for (SkinIndex i(0); i < scene.NumSkins(); ++i) { + const Skin *const skin = scene.GetSkin(i); + DRACO_ASSIGN_OR_RETURN( + const int output_accessor_index, + AddNodeAnimationData(skin->GetInverseBindMatrices())); + + std::unique_ptr encoder_skin(new EncoderSkin); + encoder_skin->inverse_bind_matrices_index = output_accessor_index; + encoder_skin->joints.reserve(skin->NumJoints()); + for (int j = 0; j < skin->NumJoints(); j++) { + encoder_skin->joints.push_back(skin->GetJoint(j).value()); + } + encoder_skin->skeleton_index = skin->GetJointRoot().value(); + skins_.push_back(std::move(encoder_skin)); + } + return OkStatus(); +} + +Status GltfAsset::AddLights(const Scene &scene) { + if (scene.NumLights() == 0) { + return OkStatus(); + } + + for (LightIndex i(0); i < scene.NumLights(); ++i) { + std::unique_ptr light = std::unique_ptr(new Light()); + light->Copy(*scene.GetLight(i)); + lights_.push_back(std::move(light)); + } + return OkStatus(); +} + +Status GltfAsset::AddMaterialsVariantsNames(const Scene &scene) { + const MaterialLibrary &library = scene.GetMaterialLibrary(); + for (int i = 0; i < library.NumMaterialsVariants(); ++i) { + materials_variants_names_.push_back(library.GetMaterialsVariantName(i)); + } + return OkStatus(); +} + +Status GltfAsset::AddInstanceArrays(const Scene &scene) { + if (scene.NumInstanceArrays() == 0) { + return OkStatus(); + } + + // Add each of the instance arrays. + std::vector t_data; + std::vector r_data; + std::vector s_data; + for (InstanceArrayIndex i(0); i < scene.NumInstanceArrays(); ++i) { + // Find which of the optional TRS components are set. + // TODO(vytyaz): Treat default TRS component vectors as absent. + const InstanceArray &array = *scene.GetInstanceArray(i); + bool is_t_set = false; + bool is_r_set = false; + bool is_s_set = false; + for (int i = 0; i < array.NumInstances(); i++) { + const InstanceArray::Instance &instance = array.GetInstance(i); + if (instance.trs.TranslationSet()) { + is_t_set = true; + } + if (instance.trs.RotationSet()) { + is_r_set = true; + } + if (instance.trs.ScaleSet()) { + is_s_set = true; + } + } + + // Create contiguous data vectors for individual TRS components. + t_data.clear(); + r_data.clear(); + s_data.clear(); + if (is_t_set) { + t_data.reserve(array.NumInstances() * 3); + } + if (is_r_set) { + r_data.reserve(array.NumInstances() * 4); + } + if (is_s_set) { + s_data.reserve(array.NumInstances() * 3); + } + + // Add TRS vectors of each instance to corresponding data vectors. + for (int i = 0; i < array.NumInstances(); i++) { + const InstanceArray::Instance &instance = array.GetInstance(i); + if (is_t_set) { + DRACO_ASSIGN_OR_RETURN(const auto &t_vector, + instance.trs.Translation()); + t_data.push_back(t_vector.x()); + t_data.push_back(t_vector.y()); + t_data.push_back(t_vector.z()); + } + if (is_r_set) { + DRACO_ASSIGN_OR_RETURN(const auto &r_vector, instance.trs.Rotation()); + r_data.push_back(r_vector.x()); + r_data.push_back(r_vector.y()); + r_data.push_back(r_vector.z()); + r_data.push_back(r_vector.w()); + } + if (is_s_set) { + DRACO_ASSIGN_OR_RETURN(const auto &s_vector, instance.trs.Scale()); + s_data.push_back(s_vector.x()); + s_data.push_back(s_vector.y()); + s_data.push_back(s_vector.z()); + } + } + + // Add TRS vectors to attribute buffers and collect their accessor indices. + EncoderInstanceArray accessors; + if (is_t_set) { + DRACO_ASSIGN_OR_RETURN(accessors.translation, AddData(t_data, 3)); + } + if (is_r_set) { + DRACO_ASSIGN_OR_RETURN(accessors.rotation, AddData(r_data, 4)); + } + if (is_s_set) { + DRACO_ASSIGN_OR_RETURN(accessors.scale, AddData(s_data, 3)); + } + + // Store accessors for later to encode as EXT_mesh_gpu_instancing extension. + instance_arrays_.push_back(accessors); + } + return OkStatus(); +} + +template +void GltfAsset::AddStructuralMetadata(const GeometryT &geometry) { + const StructuralMetadata &structural_metadata = + geometry.GetStructuralMetadata(); + if (!structural_metadata.GetPropertyTableSchema().Empty()) { + property_table_schema_ = structural_metadata.GetPropertyTableSchema(); + for (int i = 0; i < structural_metadata.NumPropertyTables(); ++i) { + property_tables_.push_back(&structural_metadata.GetPropertyTable(i)); + } + } +} + +StatusOr GltfAsset::AddData(const std::vector &data, + int num_components) { + std::string type; + switch (num_components) { + case 3: + type = "VEC3"; + break; + case 4: + type = "VEC4"; + break; + default: + return ErrorStatus("Unsupported number of components."); + } + + const size_t buffer_start_offset = buffer_.size(); + + std::vector min_values(num_components); + for (int j = 0; j < num_components; ++j) { + min_values[j] = data[j]; + } + std::vector max_values = min_values; + + const int count = data.size() / num_components; + for (int i = 0; i < count; ++i) { + for (int j = 0; j < num_components; ++j) { + const float value = data[(i * num_components) + j]; + if (value < min_values[j]) { + min_values[j] = value; + } + if (value > max_values[j]) { + max_values[j] = value; + } + buffer_.Encode(&value, sizeof(float)); + } + } + + if (!PadBuffer()) { + return ErrorStatus("AddArray: PadBuffer returned DRACO_ERROR."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + GltfAccessor accessor; + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + accessor.component_type = ComponentType::FLOAT; + accessor.count = count; + for (int j = 0; j < num_components; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + accessor.type = type; + accessor.normalized = false; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +StatusOr GltfAsset::AddBufferView( + const PropertyTable::Property::Data &data) { + const size_t buffer_start_offset = buffer_.size(); + buffer_.Encode(data.data.data(), data.data.size()); + if (!PadBuffer()) { + return ErrorStatus("AddBufferView: PadBuffer returned DRACO_ERROR."); + } + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_view.target = data.target; + buffer_views_.push_back(buffer_view); + return static_cast(buffer_views_.size() - 1); +} + +bool GltfAsset::EncodeAssetProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginObject("asset"); + gltf_json_.OutputValue("version", version_); + gltf_json_.OutputValue("generator", generator_); + gltf_json_.EndObject(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeScenesProperty(EncoderBuffer *buf_out) { + // We currently only support one scene. + gltf_json_.BeginArray("scenes"); + gltf_json_.BeginObject(); + gltf_json_.BeginArray("nodes"); + + for (int i = 0; i < nodes_.size(); ++i) { + if (nodes_[i].root_node) { + gltf_json_.OutputValue(i); + } + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeInitialSceneProperty(EncoderBuffer *buf_out) { + gltf_json_.OutputValue("scene", scene_index_); + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeNodesProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("nodes"); + + for (int i = 0; i < nodes_.size(); ++i) { + gltf_json_.BeginObject(); + if (!nodes_[i].name.empty()) { + gltf_json_.OutputValue("name", nodes_[i].name); + } + if (nodes_[i].mesh_index >= 0) { + gltf_json_.OutputValue("mesh", nodes_[i].mesh_index); + } + if (nodes_[i].skin_index >= 0) { + gltf_json_.OutputValue("skin", nodes_[i].skin_index); + } + if (nodes_[i].instance_array_index >= 0 || nodes_[i].light_index >= 0) { + gltf_json_.BeginObject("extensions"); + if (nodes_[i].instance_array_index >= 0) { + gltf_json_.BeginObject("EXT_mesh_gpu_instancing"); + gltf_json_.BeginObject("attributes"); + const int index = nodes_[i].instance_array_index; + const EncoderInstanceArray &accessors = instance_arrays_[index]; + if (accessors.translation != -1) { + gltf_json_.OutputValue("TRANSLATION", accessors.translation); + } + if (accessors.rotation != -1) { + gltf_json_.OutputValue("ROTATION", accessors.rotation); + } + if (accessors.scale != -1) { + gltf_json_.OutputValue("SCALE", accessors.scale); + } + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } + if (nodes_[i].light_index >= 0) { + gltf_json_.BeginObject("KHR_lights_punctual"); + gltf_json_.OutputValue("light", nodes_[i].light_index); + gltf_json_.EndObject(); + } + gltf_json_.EndObject(); + } + + if (!nodes_[i].childern_indices.empty()) { + gltf_json_.BeginArray("children"); + for (int j = 0; j < nodes_[i].childern_indices.size(); ++j) { + gltf_json_.OutputValue(nodes_[i].childern_indices[j]); + } + gltf_json_.EndArray(); + } + + if (!nodes_[i].trs_matrix.IsMatrixIdentity()) { + const auto maybe_transformation = nodes_[i].trs_matrix.Matrix(); + const auto transformation = maybe_transformation.ValueOrDie(); + + if (nodes_[i].trs_matrix.IsMatrixTranslationOnly()) { + gltf_json_.BeginArray("translation"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(transformation(j, 3)); + } + gltf_json_.EndArray(); + } else { + gltf_json_.BeginArray("matrix"); + for (int j = 0; j < 4; ++j) { + for (int k = 0; k < 4; ++k) { + gltf_json_.OutputValue(transformation(k, j)); + } + } + gltf_json_.EndArray(); + } + } else { + if (nodes_[i].trs_matrix.TranslationSet()) { + const auto maybe_translation = nodes_[i].trs_matrix.Translation(); + const auto translation = maybe_translation.ValueOrDie(); + gltf_json_.BeginArray("translation"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(translation[j]); + } + gltf_json_.EndArray(); + } + if (nodes_[i].trs_matrix.RotationSet()) { + const auto maybe_rotation = nodes_[i].trs_matrix.Rotation(); + const auto rotation = maybe_rotation.ValueOrDie(); + gltf_json_.BeginArray("rotation"); + for (int j = 0; j < 4; ++j) { + // Note: coeffs() returns quaternion values as (x, y, z, w) which is + // the expected format of glTF. + gltf_json_.OutputValue(rotation.coeffs()[j]); + } + gltf_json_.EndArray(); + } + if (nodes_[i].trs_matrix.ScaleSet()) { + const auto maybe_scale = nodes_[i].trs_matrix.Scale(); + const auto scale = maybe_scale.ValueOrDie(); + gltf_json_.BeginArray("scale"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(scale[j]); + } + gltf_json_.EndArray(); + } + } + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +Status GltfAsset::EncodeMeshesProperty(EncoderBuffer *buf_out) { + mesh_features_texture_index_ = 0; + gltf_json_.BeginArray("meshes"); + + for (int i = 0; i < meshes_.size(); ++i) { + gltf_json_.BeginObject(); + + if (!meshes_[i].name.empty()) { + gltf_json_.OutputValue("name", meshes_[i].name); + } + + if (!meshes_[i].primitives.empty()) { + gltf_json_.BeginArray("primitives"); + + for (int j = 0; j < meshes_[i].primitives.size(); ++j) { + const GltfPrimitive &primitive = meshes_[i].primitives[j]; + gltf_json_.BeginObject(); + + gltf_json_.BeginObject("attributes"); + for (auto const &it : primitive.attributes) { + gltf_json_.OutputValue(it.first, it.second); + } + gltf_json_.EndObject(); + + if (primitive.indices >= 0) { + gltf_json_.OutputValue("indices", primitive.indices); + } + gltf_json_.OutputValue("mode", primitive.mode); + if (primitive.material >= 0) { + gltf_json_.OutputValue("material", primitive.material); + } + DRACO_RETURN_IF_ERROR( + EncodePrimitiveExtensionsProperty(primitive, buf_out)); + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + } + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return ErrorStatus("Failed encoding meshes."); + } + return OkStatus(); +} + +Status GltfAsset::EncodePrimitiveExtensionsProperty( + const GltfPrimitive &primitive, EncoderBuffer *buf_out) { + // Return if the primitive has no extensions to encode. + const bool has_draco_mesh_compression = + primitive.compressed_mesh_info.buffer_view_index >= 0; + const bool has_materials_variants = + !primitive.material_variants_mappings.empty(); + const bool has_mesh_features = !primitive.mesh_features.empty(); + if (!has_draco_mesh_compression && !has_materials_variants && + !has_mesh_features) { + return OkStatus(); + } + + // Encode primitive extensions. + gltf_json_.BeginObject("extensions"); + if (has_draco_mesh_compression) { + gltf_json_.BeginObject("KHR_draco_mesh_compression"); + gltf_json_.OutputValue("bufferView", + primitive.compressed_mesh_info.buffer_view_index); + gltf_json_.BeginObject("attributes"); + for (auto const &it : primitive.compressed_mesh_info.attributes) { + gltf_json_.OutputValue(it.first, it.second); + } + gltf_json_.EndObject(); // attributes entry. + gltf_json_.EndObject(); // KHR_draco_mesh_compression entry. + } + if (has_materials_variants) { + gltf_json_.BeginObject("KHR_materials_variants"); + gltf_json_.BeginArray("mappings"); + for (const auto &mapping : primitive.material_variants_mappings) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("material", mapping.material); + gltf_json_.BeginArray("variants"); + for (const int variant : mapping.variants) { + gltf_json_.OutputValue(variant); + } + gltf_json_.EndArray(); // variants array. + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); // mappings array. + gltf_json_.EndObject(); // KHR_materials_variants entry. + } + if (has_mesh_features) { + gltf_json_.BeginObject("EXT_mesh_features"); + gltf_json_.BeginArray("featureIds"); + for (int i = 0; i < primitive.mesh_features.size(); i++) { + const auto &features = primitive.mesh_features[i]; + gltf_json_.BeginObject(); + if (!features->GetLabel().empty()) { + gltf_json_.OutputValue("label", features->GetLabel()); + } + gltf_json_.OutputValue("featureCount", features->GetFeatureCount()); + if (features->GetAttributeIndex() != -1) { + gltf_json_.OutputValue("attribute", features->GetAttributeIndex()); + } + if (features->GetPropertyTableIndex() != -1) { + gltf_json_.OutputValue("propertyTable", + features->GetPropertyTableIndex()); + } + if (features->GetTextureMap().tex_coord_index() != -1) { + const TextureMap &texture_map = features->GetTextureMap(); + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *texture_map.texture(), mesh_features_texture_index_++, + "_MeshFeatures"); + + // Save image as RGBA if the A channel is used to store feature ID. + const auto &channels = features->GetTextureChannels(); + const int num_channels = + std::count(channels.begin(), channels.end(), 3) == 1 ? 4 : 3; + DRACO_ASSIGN_OR_RETURN( + const int image_index, + AddImage(texture_stem, texture_map.texture(), num_channels)); + const int tex_coord_index = texture_map.tex_coord_index(); + Material dummy_material; + DRACO_RETURN_IF_ERROR(EncodeTextureMap("texture", image_index, + tex_coord_index, dummy_material, + texture_map, channels)); + } + if (features->GetNullFeatureId() != -1) { + gltf_json_.OutputValue("nullFeatureId", features->GetNullFeatureId()); + } + gltf_json_.EndObject(); + mesh_features_used_ = true; + } + gltf_json_.EndArray(); // featureIds array. + gltf_json_.EndObject(); // EXT_mesh_features entry. + } + gltf_json_.EndObject(); // extensions entry. + return OkStatus(); +} + +Status GltfAsset::EncodeMaterials(EncoderBuffer *buf_out) { + // Check if we have textures to write. + if (material_library_.NumMaterials() == 0) { + return EncodeDefaultMaterial(buf_out); + } + return EncodeMaterialsProperty(buf_out); +} + +void GltfAsset::EncodeColorMaterial(float red, float green, float blue, + float alpha, float metallic_factor) { + gltf_json_.BeginObject("pbrMetallicRoughness"); + + gltf_json_.BeginArray("baseColorFactor"); + gltf_json_.OutputValue(red); + gltf_json_.OutputValue(green); + gltf_json_.OutputValue(blue); + gltf_json_.OutputValue(alpha); + gltf_json_.EndArray(); + gltf_json_.OutputValue("metallicFactor", metallic_factor); + + gltf_json_.EndObject(); // pbrMetallicRoughness +} + +Status GltfAsset::EncodeDefaultMaterial(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("materials"); + gltf_json_.BeginObject(); + EncodeColorMaterial(0.75, 0.75, 0.75, 1.0, 0.0); + gltf_json_.EndObject(); + gltf_json_.EndArray(); // materials + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Error encoding default material."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeTextureMap(const std::string &object_name, + int image_index, int tex_coord_index, + const Material &material, + const TextureMap &texture_map) { + return EncodeTextureMap(object_name, image_index, tex_coord_index, material, + texture_map, {}); +} + +Status GltfAsset::EncodeTextureMap(const std::string &object_name, + int image_index, int tex_coord_index, + const Material &material, + const TextureMap &texture_map, + const std::vector &channels) { + // Create a new texture sampler (or reuse an existing one if possible). + const TextureSampler sampler(texture_map.min_filter(), + texture_map.mag_filter(), + texture_map.wrapping_mode()); + DRACO_ASSIGN_OR_RETURN(const int sampler_index, AddTextureSampler(sampler)); + + // Check if we can reuse an existing texture object. + const GltfTexture texture(image_index, sampler_index); + const auto texture_it = + std::find(textures_.begin(), textures_.end(), texture); + int texture_index; + if (texture_it == textures_.end()) { + // Create a new texture object for this texture map. + texture_index = textures_.size(); + textures_.push_back(GltfTexture(image_index, sampler_index)); + } else { + // Reuse an existing texture object. + texture_index = std::distance(textures_.begin(), texture_it); + } + + gltf_json_.BeginObject(object_name); + gltf_json_.OutputValue("index", texture_index); + gltf_json_.OutputValue("texCoord", tex_coord_index); + if (object_name == "normalTexture") { + const float scale = material.GetNormalTextureScale(); + if (scale != 1.0f) { + gltf_json_.OutputValue("scale", scale); + } + } + + // The "texture" object of the EXT_mesh_features extension has a custom + // property "channels" that is encoded here. + if (object_name == "texture" && !channels.empty()) { + gltf_json_.BeginArray("channels"); + for (const int channel : channels) { + gltf_json_.OutputValue(channel); + } + gltf_json_.EndArray(); // channels array. + } + + // Check if |texture_map| is using the KHR_texture_transform extension. + if (!TextureTransform::IsDefault(texture_map.texture_transform())) { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("KHR_texture_transform"); + if (texture_map.texture_transform().IsOffsetSet()) { + const std::array &offset = + texture_map.texture_transform().offset(); + gltf_json_.BeginArray("offset"); + gltf_json_.OutputValue(offset[0]); + gltf_json_.OutputValue(offset[1]); + gltf_json_.EndArray(); + } + if (texture_map.texture_transform().IsRotationSet()) { + gltf_json_.OutputValue("rotation", + texture_map.texture_transform().rotation()); + } + if (texture_map.texture_transform().IsScaleSet()) { + const std::array &scale = + texture_map.texture_transform().scale(); + gltf_json_.BeginArray("scale"); + gltf_json_.OutputValue(scale[0]); + gltf_json_.OutputValue(scale[1]); + gltf_json_.EndArray(); + } + // TODO(fgalligan): The spec says the extension is not required if the + // pre-transform and the post-transform tex coords are the same. But I'm not + // sure why. I have filed a bug asking for clarification. + // https://github.com/KhronosGroup/glTF/issues/1724 + if (texture_map.texture_transform().IsTexCoordSet()) { + gltf_json_.OutputValue("texCoord", + texture_map.texture_transform().tex_coord()); + } else { + extensions_required_.insert("KHR_texture_transform"); + } + gltf_json_.EndObject(); + gltf_json_.EndObject(); + + extensions_used_.insert("KHR_texture_transform"); + } + gltf_json_.EndObject(); + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialsProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("materials"); + for (int i = 0; i < material_library_.NumMaterials(); ++i) { + const Material *const material = material_library_.GetMaterial(i); + if (!material) { + return Status(Status::DRACO_ERROR, "Error getting material."); + } + + const TextureMap *const color = + material->GetTextureMapByType(TextureMap::COLOR); + const TextureMap *const metallic = + material->GetTextureMapByType(TextureMap::METALLIC_ROUGHNESS); + const TextureMap *const normal = + material->GetTextureMapByType(TextureMap::NORMAL_TANGENT_SPACE); + const TextureMap *const occlusion = + material->GetTextureMapByType(TextureMap::AMBIENT_OCCLUSION); + const TextureMap *const emissive = + material->GetTextureMapByType(TextureMap::EMISSIVE); + + // Check if material is unlit and does not have a fallback. + if (material->GetUnlit() && + (!color || metallic || normal || occlusion || emissive || + material->GetMetallicFactor() != 0.0 || + material->GetRoughnessFactor() <= 0.5 || + material->GetEmissiveFactor() != Vector3f(0.0, 0.0, 0.0))) { + // If we find one material that is unlit and does not contain a fallback + // we must set "KHR_materials_unlit" in extensions reqruied for the entire + // glTF file. + extensions_required_.insert("KHR_materials_unlit"); + } + + int occlusion_metallic_roughness_image_index = -1; + + gltf_json_.BeginObject(); // material object. + + gltf_json_.BeginObject("pbrMetallicRoughness"); + if (color) { + const bool rgba = true; // Unused for now. + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *color->texture(), i, "_BaseColor"); + DRACO_ASSIGN_OR_RETURN( + const int color_image_index, + AddImage(texture_stem, color->texture(), rgba ? 4 : 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("baseColorTexture", color_image_index, + color->tex_coord_index(), *material, *color)); + } + // Try to combine metallic and occlusion only if they have the same tex + // coord index. + // TODO(b/145991271): Check out if we need to check texture indices. + if (metallic && occlusion && + metallic->tex_coord_index() == occlusion->tex_coord_index()) { + if (metallic->texture() == occlusion->texture()) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *metallic->texture(), i, "_OcclusionMetallicRoughness"); + // Metallic and occlusion textures are already combined. + DRACO_ASSIGN_OR_RETURN(occlusion_metallic_roughness_image_index, + AddImage(texture_stem, metallic->texture(), 3)); + } + if (occlusion_metallic_roughness_image_index != -1) + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "metallicRoughnessTexture", + occlusion_metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } + + if (metallic && occlusion_metallic_roughness_image_index == -1) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *metallic->texture(), i, "_MetallicRoughness"); + DRACO_ASSIGN_OR_RETURN(const int metallic_roughness_image_index, + AddImage(texture_stem, metallic->texture(), 3)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "metallicRoughnessTexture", metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } + + EncodeVectorArray("baseColorFactor", material->GetColorFactor()); + gltf_json_.OutputValue("metallicFactor", material->GetMetallicFactor()); + gltf_json_.OutputValue("roughnessFactor", material->GetRoughnessFactor()); + gltf_json_.EndObject(); // pbrMetallicRoughness + + if (normal) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *normal->texture(), i, "_Normal"); + DRACO_ASSIGN_OR_RETURN(const int normal_image_index, + AddImage(texture_stem, normal->texture(), 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("normalTexture", normal_image_index, + normal->tex_coord_index(), *material, *normal)); + } + + if (occlusion_metallic_roughness_image_index != -1) { + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "occlusionTexture", occlusion_metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } else if (occlusion) { + // Store occlusion texture in a grayscale format, unless it is used by + // metallic-roughness map of some other matierial. It is possible that + // this material uses occlusion (R channel) and some other material uses + // metallic-roughness (GB channels) from this texture. + const int num_components = TextureUtils::ComputeRequiredNumChannels( + *occlusion->texture(), material_library_); + const std::string suffix = + (num_components == 1) ? "_Occlusion" : "_OcclusionMetallicRoughness"; + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *occlusion->texture(), i, suffix); + DRACO_ASSIGN_OR_RETURN( + const int occlusion_image_index, + AddImage(texture_stem, occlusion->texture(), num_components)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "occlusionTexture", occlusion_image_index, + occlusion->tex_coord_index(), *material, *occlusion)); + } + + if (emissive) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *emissive->texture(), i, "_Emissive"); + DRACO_ASSIGN_OR_RETURN(const int emissive_image_index, + AddImage(texture_stem, emissive->texture(), 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("emissiveTexture", emissive_image_index, + emissive->tex_coord_index(), *material, *emissive)); + } + + EncodeVectorArray("emissiveFactor", + material->GetEmissiveFactor()); + + switch (material->GetTransparencyMode()) { + case Material::TransparencyMode::TRANSPARENCY_MASK: + gltf_json_.OutputValue("alphaMode", "MASK"); + gltf_json_.OutputValue("alphaCutoff", material->GetAlphaCutoff()); + break; + case Material::TransparencyMode::TRANSPARENCY_BLEND: + gltf_json_.OutputValue("alphaMode", "BLEND"); + break; + default: + gltf_json_.OutputValue("alphaMode", "OPAQUE"); + break; + } + if (!material->GetName().empty()) { + gltf_json_.OutputValue("name", material->GetName()); + } + + // Output doubleSided if different than the default. + if (material->GetDoubleSided()) { + gltf_json_.OutputValue("doubleSided", material->GetDoubleSided()); + } + + // Encode material extensions if any. + if (material->GetUnlit() || material->HasSheen() || + material->HasTransmission() || material->HasClearcoat() || + material->HasVolume() || material->HasIor() || + material->HasSpecular()) { + gltf_json_.BeginObject("extensions"); + + // Encode individual material extensions. + if (material->GetUnlit()) { + EncodeMaterialUnlitExtension(*material); + } else { + // PBR extensions can only be added to non-unlit materials. + Material defaults; + if (material->HasSheen()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialSheenExtension(*material, defaults, i)); + } + if (material->HasTransmission()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialTransmissionExtension(*material, defaults, i)); + } + if (material->HasClearcoat()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialClearcoatExtension(*material, defaults, i)); + } + if (material->HasVolume()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialVolumeExtension(*material, defaults, i)); + } + if (material->HasIor()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialIorExtension(*material, defaults)); + } + if (material->HasSpecular()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialSpecularExtension(*material, defaults, i)); + } + } + + gltf_json_.EndObject(); // extensions object. + } + + gltf_json_.EndObject(); // material object. + } + + gltf_json_.EndArray(); // materials array. + + if (!textures_.empty()) { + gltf_json_.BeginArray("textures"); + for (int i = 0; i < textures_.size(); ++i) { + const int image_index = textures_[i].image_index; + gltf_json_.BeginObject(); + if (images_[image_index].mime_type == "image/webp") { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("EXT_texture_webp"); + gltf_json_.OutputValue("source", image_index); + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } else if (images_[image_index].mime_type == "image/ktx2") { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("KHR_texture_basisu"); + gltf_json_.OutputValue("source", image_index); + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } else { + gltf_json_.OutputValue("source", image_index); + } + if (textures_[i].sampler_index >= 0) { + gltf_json_.OutputValue("sampler", textures_[i].sampler_index); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + if (!texture_samplers_.empty()) { + gltf_json_.BeginArray("samplers"); + for (int i = 0; i < texture_samplers_.size(); ++i) { + gltf_json_.BeginObject(); + + const int mode_s = TextureAxisWrappingModeToGltfValue( + texture_samplers_[i].wrapping_mode.s); + const int mode_t = TextureAxisWrappingModeToGltfValue( + texture_samplers_[i].wrapping_mode.t); + gltf_json_.OutputValue("wrapS", mode_s); + gltf_json_.OutputValue("wrapT", mode_t); + + if (texture_samplers_[i].min_filter != TextureMap::UNSPECIFIED) { + gltf_json_.OutputValue( + "minFilter", + TextureFilterTypeToGltfValue(texture_samplers_[i].min_filter)); + } + if (texture_samplers_[i].mag_filter != TextureMap::UNSPECIFIED) { + gltf_json_.OutputValue( + "magFilter", + TextureFilterTypeToGltfValue(texture_samplers_[i].mag_filter)); + } + + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + if (!images_.empty()) { + gltf_json_.BeginArray("images"); + for (int i = 0; i < images_.size(); ++i) { + if (add_images_to_buffer_) { + DRACO_RETURN_IF_ERROR(SaveImageToBuffer(i)); + } + gltf_json_.BeginObject(); + if (images_[i].buffer_view >= 0) { + gltf_json_.OutputValue("bufferView", images_[i].buffer_view); + gltf_json_.OutputValue("mimeType", images_[i].mime_type); + } else { + gltf_json_.OutputValue("uri", images_[i].image_name); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Error encoding materials."); + } + return OkStatus(); +} + +void GltfAsset::EncodeMaterialUnlitExtension(const Material &material) { + extensions_used_.insert("KHR_materials_unlit"); + gltf_json_.BeginObject("KHR_materials_unlit"); + gltf_json_.EndObject(); +} + +Status GltfAsset::EncodeMaterialSheenExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_sheen"); + gltf_json_.BeginObject("KHR_materials_sheen"); + + // Add sheen color factor, unless it is the default. + if (material.GetSheenColorFactor() != defaults.GetSheenColorFactor()) { + EncodeVectorArray("sheenColorFactor", + material.GetSheenColorFactor()); + } + + // Add sheen roughness factor, unless it is the default. + if (material.GetSheenRoughnessFactor() != + defaults.GetSheenRoughnessFactor()) { + gltf_json_.OutputValue("sheenRoughnessFactor", + material.GetSheenRoughnessFactor()); + } + + // Add sheen color texture (RGB channels) if present. + // TODO(vytyaz): Combine sheen color and roughness images if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("sheenColorTexture", "_SheenColor", + TextureMap::SHEEN_COLOR, -1, material, + material_index)); + + // Add sheen roughness texture (A channel) if present. + DRACO_RETURN_IF_ERROR( + EncodeTexture("sheenRoughnessTexture", "_SheenRoughness", + TextureMap::SHEEN_ROUGHNESS, 4, material, material_index)); + + gltf_json_.EndObject(); // KHR_materials_sheen object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialTransmissionExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_transmission"); + gltf_json_.BeginObject("KHR_materials_transmission"); + + // Add transmission factor, unless it is the default. + if (material.GetTransmissionFactor() != defaults.GetTransmissionFactor()) { + gltf_json_.OutputValue("transmissionFactor", + material.GetTransmissionFactor()); + } + + // Add transmission texture (R channel) if present. + // TODO(vytyaz): Store texture in a grayscale format if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("transmissionTexture", "_Transmission", + TextureMap::TRANSMISSION, 3, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_transmission object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialClearcoatExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_clearcoat"); + gltf_json_.BeginObject("KHR_materials_clearcoat"); + + // Add clearcoat factor, unless it is the default. + if (material.GetClearcoatFactor() != defaults.GetClearcoatFactor()) { + gltf_json_.OutputValue("clearcoatFactor", material.GetClearcoatFactor()); + } + + // Add clearcoat roughness factor, unless it is the default. + if (material.GetClearcoatRoughnessFactor() != + defaults.GetClearcoatRoughnessFactor()) { + gltf_json_.OutputValue("clearcoatRoughnessFactor", + material.GetClearcoatRoughnessFactor()); + } + + // Add clearcoat texture (R channel) if present. + // TODO(vytyaz): Combine clearcoat and clearcoat roughness images if possible. + // TODO(vytyaz): Store texture in a grayscale format if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("clearcoatTexture", "_Clearcoat", + TextureMap::CLEARCOAT, 3, material, + material_index)); + + // Add clearcoat roughness texture (G channel) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture( + "clearcoatRoughnessTexture", "_ClearcoatRoughness", + TextureMap::CLEARCOAT_ROUGHNESS, 3, material, material_index)); + + // Add clearcoat normal texture (RGB channels) if present. + DRACO_RETURN_IF_ERROR( + EncodeTexture("clearcoatNormalTexture", "_ClearcoatNormal", + TextureMap::CLEARCOAT_NORMAL, 3, material, material_index)); + + gltf_json_.EndObject(); // KHR_materials_clearcoat object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialVolumeExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_volume"); + gltf_json_.BeginObject("KHR_materials_volume"); + + // Add thickness factor, unless it is the default. + if (material.GetThicknessFactor() != defaults.GetThicknessFactor()) { + gltf_json_.OutputValue("thicknessFactor", material.GetThicknessFactor()); + } + + // Add attenuation distance, unless it is the default. + if (material.GetAttenuationDistance() != defaults.GetAttenuationDistance()) { + gltf_json_.OutputValue("attenuationDistance", + material.GetAttenuationDistance()); + } + + // Add attenuation color, unless it is the default. + if (material.GetAttenuationColor() != defaults.GetAttenuationColor()) { + EncodeVectorArray("attenuationColor", + material.GetAttenuationColor()); + } + + // Add thickness texture (G channel) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture("thicknessTexture", "_Thickness", + TextureMap::THICKNESS, 3, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_volume object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialIorExtension(const Material &material, + const Material &defaults) { + extensions_used_.insert("KHR_materials_ior"); + gltf_json_.BeginObject("KHR_materials_ior"); + + // Add ior, unless it is the default. + if (material.GetIor() != defaults.GetIor()) { + gltf_json_.OutputValue("ior", material.GetIor()); + } + + gltf_json_.EndObject(); // KHR_materials_ior object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialSpecularExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_specular"); + gltf_json_.BeginObject("KHR_materials_specular"); + + // Add specular factor, unless it is the default. + if (material.GetSpecularFactor() != defaults.GetSpecularFactor()) { + gltf_json_.OutputValue("specularFactor", material.GetSpecularFactor()); + } + + // Add specular color factor, unless it is the default. + if (material.GetSpecularColorFactor() != defaults.GetSpecularColorFactor()) { + EncodeVectorArray("specularColorFactor", + material.GetSpecularColorFactor()); + } + + // Add specular texture (A channel) if present. + // TODO(vytyaz): Combine specular and specular color images if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("specularTexture", "_Specular", + TextureMap::SPECULAR, 4, material, + material_index)); + + // Add specular color texture (RGB channels) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture("specularColorTexture", "_SpecularColor", + TextureMap::SPECULAR_COLOR, -1, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_specular object. + + return OkStatus(); +} + +Status GltfAsset::EncodeTexture(const std::string &name, + const std::string &stem_suffix, + TextureMap::Type type, int num_components, + const Material &material, int material_index) { + const TextureMap *const texture_map = material.GetTextureMapByType(type); + if (texture_map) { + if (num_components == -1) { + const bool rgba = true; // Unused for now. + num_components = rgba ? 4 : 3; + } + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *texture_map->texture(), material_index, stem_suffix); + DRACO_ASSIGN_OR_RETURN( + const int image_index, + AddImage(texture_stem, texture_map->texture(), num_components)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap(name, image_index, + texture_map->tex_coord_index(), + material, *texture_map)); + } + return OkStatus(); +} + +Status GltfAsset::EncodeAnimationsProperty(EncoderBuffer *buf_out) { + if (animations_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginArray("animations"); + for (int i = 0; i < animations_.size(); ++i) { + gltf_json_.BeginObject(); + + if (!animations_[i]->name.empty()) { + gltf_json_.OutputValue("name", animations_[i]->name); + } + + gltf_json_.BeginArray("samplers"); + for (int j = 0; j < animations_[i]->samplers.size(); ++j) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("input", animations_[i]->samplers[j]->input_index); + gltf_json_.OutputValue( + "interpolation", + AnimationSampler::InterpolationToString( + animations_[i]->samplers[j]->interpolation_type)); + gltf_json_.OutputValue("output", + animations_[i]->samplers[j]->output_index); + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + + gltf_json_.BeginArray("channels"); + for (int j = 0; j < animations_[i]->channels.size(); ++j) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("sampler", + animations_[i]->channels[j]->sampler_index); + + gltf_json_.BeginObject("target"); + gltf_json_.OutputValue("node", animations_[i]->channels[j]->target_index); + gltf_json_.OutputValue( + "path", AnimationChannel::TransformationToString( + animations_[i]->channels[j]->transformation_type)); + gltf_json_.EndObject(); + + gltf_json_.EndObject(); // Channel entry. + } + gltf_json_.EndArray(); + + gltf_json_.EndObject(); // Animmation entry. + } + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode animations."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeSkinsProperty(EncoderBuffer *buf_out) { + if (skins_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginArray("skins"); + for (int i = 0; i < skins_.size(); ++i) { + gltf_json_.BeginObject(); + + if (skins_[i]->inverse_bind_matrices_index >= 0) { + gltf_json_.OutputValue("inverseBindMatrices", + skins_[i]->inverse_bind_matrices_index); + } + if (skins_[i]->skeleton_index >= 0) { + gltf_json_.OutputValue("skeleton", skins_[i]->skeleton_index); + } + + if (!skins_[i]->joints.empty()) { + gltf_json_.BeginArray("joints"); + for (int j = 0; j < skins_[i]->joints.size(); ++j) { + gltf_json_.OutputValue(skins_[i]->joints[j]); + } + gltf_json_.EndArray(); + } + gltf_json_.EndObject(); // Skin entry. + } + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode animations."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeTopLevelExtensionsProperty(EncoderBuffer *buf_out) { + // Return if there are no top-level asset extensions to encode. + if (lights_.empty() && materials_variants_names_.empty() && + property_tables_.empty()) { + return OkStatus(); + } + + // Encode top-level extensions. + gltf_json_.BeginObject("extensions"); + DRACO_RETURN_IF_ERROR(EncodeLightsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeMaterialsVariantsNamesProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeStructuralMetadataProperty(buf_out)); + gltf_json_.EndObject(); // extensions entry. + return OkStatus(); +} + +Status GltfAsset::EncodeLightsProperty(EncoderBuffer *buf_out) { + if (lights_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("KHR_lights_punctual"); + gltf_json_.BeginArray("lights"); + const Light defaults; + for (const auto &light : lights_) { + gltf_json_.BeginObject(); + if (light->GetName() != defaults.GetName()) { + gltf_json_.OutputValue("name", light->GetName()); + } + if (light->GetColor() != defaults.GetColor()) { + gltf_json_.BeginArray("color"); + gltf_json_.OutputValue(light->GetColor()[0]); + gltf_json_.OutputValue(light->GetColor()[1]); + gltf_json_.OutputValue(light->GetColor()[2]); + gltf_json_.EndArray(); + } + if (light->GetIntensity() != defaults.GetIntensity()) { + gltf_json_.OutputValue("intensity", light->GetIntensity()); + } + switch (light->GetType()) { + case Light::DIRECTIONAL: + gltf_json_.OutputValue("type", "directional"); + break; + case Light::POINT: + gltf_json_.OutputValue("type", "point"); + break; + case Light::SPOT: + gltf_json_.OutputValue("type", "spot"); + break; + } + if (light->GetRange() != defaults.GetRange()) { + gltf_json_.OutputValue("range", light->GetRange()); + } + if (light->GetType() == Light::SPOT) { + gltf_json_.BeginObject("spot"); + if (light->GetInnerConeAngle() != defaults.GetInnerConeAngle()) { + gltf_json_.OutputValue("innerConeAngle", light->GetInnerConeAngle()); + } + if (light->GetOuterConeAngle() != defaults.GetOuterConeAngle()) { + gltf_json_.OutputValue("outerConeAngle", light->GetOuterConeAngle()); + } + gltf_json_.EndObject(); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); // KHR_lights_punctual entry. + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialsVariantsNamesProperty(EncoderBuffer *buf_out) { + if (materials_variants_names_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("KHR_materials_variants"); + gltf_json_.BeginArray("variants"); + for (const std::string &name : materials_variants_names_) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("name", name); + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); // KHR_materials_variants entry. + return OkStatus(); +} + +Status GltfAsset::EncodeStructuralMetadataProperty(EncoderBuffer *buf_out) { + if (property_table_schema_.Empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("EXT_structural_metadata"); + + // Encodes property table schema. + struct SchemaWriter { + static void Write(const PropertyTable::Schema::Object &object, + JsonWriter *json_writer) { + switch (object.GetType()) { + case PropertyTable::Schema::Object::OBJECT: + json_writer->BeginObject(object.GetName()); + for (const PropertyTable::Schema::Object &obj : object.GetObjects()) { + Write(obj, json_writer); + } + json_writer->EndObject(); + break; + case PropertyTable::Schema::Object::ARRAY: + json_writer->BeginArray(object.GetName()); + for (const PropertyTable::Schema::Object &obj : object.GetArray()) { + Write(obj, json_writer); + } + json_writer->EndArray(); + break; + case PropertyTable::Schema::Object::STRING: + json_writer->OutputValue(object.GetName(), object.GetString()); + break; + case PropertyTable::Schema::Object::INTEGER: + json_writer->OutputValue(object.GetName(), object.GetInteger()); + break; + case PropertyTable::Schema::Object::BOOLEAN: + json_writer->OutputValue(object.GetName(), object.GetBoolean()); + break; + } + } + }; + + // Encode property table schema. + SchemaWriter::Write(property_table_schema_.json, &gltf_json_); + + // Encode all property tables. + gltf_json_.BeginArray("propertyTables"); + for (const PropertyTable *const table : property_tables_) { + gltf_json_.BeginObject(); + if (!table->GetName().empty()) { + gltf_json_.OutputValue("name", table->GetName()); + } + if (!table->GetClass().empty()) { + gltf_json_.OutputValue("class", table->GetClass()); + } + gltf_json_.OutputValue("count", table->GetCount()); + + // Encoder all property table properties. + gltf_json_.BeginObject("properties"); + for (int i = 0; i < table->NumProperties(); ++i) { + const PropertyTable::Property &property = table->GetProperty(i); + gltf_json_.BeginObject(property.GetName()); + + // Encode property values. + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetData())); + gltf_json_.OutputValue("values", buffer_view_index); + + // Encode offsets for variable-length arrays. + if (!property.GetArrayOffsets().data.data.empty()) { + if (!property.GetArrayOffsets().type.empty()) { + gltf_json_.OutputValue("arrayOffsetType", + property.GetArrayOffsets().type); + } + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetArrayOffsets().data)); + gltf_json_.OutputValue("arrayOffsets", buffer_view_index); + } + + // Encode offsets for strings. + if (!property.GetStringOffsets().data.data.empty()) { + if (!property.GetStringOffsets().type.empty()) { + gltf_json_.OutputValue("stringOffsetType", + property.GetStringOffsets().type); + } + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetStringOffsets().data)); + gltf_json_.OutputValue("stringOffsets", buffer_view_index); + } + gltf_json_.EndObject(); // Named property entry. + } + gltf_json_.EndObject(); // properties entry. + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); // propertyTables entry. + gltf_json_.EndObject(); // EXT_structural_metadata entry. + return OkStatus(); +} + +bool GltfAsset::EncodeAccessorsProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("accessors"); + + for (int i = 0; i < accessors_.size(); ++i) { + gltf_json_.BeginObject(); + + if (accessors_[i].buffer_view_index >= 0) { + gltf_json_.OutputValue("bufferView", accessors_[i].buffer_view_index); + if (output_type_ == GltfEncoder::VERBOSE) { + gltf_json_.OutputValue("byteOffset", 0); + } + } + gltf_json_.OutputValue("componentType", accessors_[i].component_type); + gltf_json_.OutputValue("count", accessors_[i].count); + if (accessors_[i].normalized) { + gltf_json_.OutputValue("normalized", accessors_[i].normalized); + } + + if (!accessors_[i].max.empty()) { + gltf_json_.BeginArray("max"); + for (int j = 0; j < accessors_[i].max.size(); ++j) { + gltf_json_.OutputValue(accessors_[i].max[j]); + } + gltf_json_.EndArray(); + } + + if (!accessors_[i].min.empty()) { + gltf_json_.BeginArray("min"); + for (int j = 0; j < accessors_[i].min.size(); ++j) { + gltf_json_.OutputValue(accessors_[i].min[j]); + } + gltf_json_.EndArray(); + } + + gltf_json_.OutputValue("type", accessors_[i].type); + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeBufferViewsProperty(EncoderBuffer *buf_out) { + // We currently only support one buffer. + gltf_json_.BeginArray("bufferViews"); + + for (int i = 0; i < buffer_views_.size(); ++i) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("buffer", 0); + gltf_json_.OutputValue("byteOffset", buffer_views_[i].buffer_byte_offset); + gltf_json_.OutputValue("byteLength", buffer_views_[i].byte_length); + if (buffer_views_[i].target != 0) { + gltf_json_.OutputValue("target", buffer_views_[i].target); + } + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeBuffersProperty(EncoderBuffer *buf_out) { + if (buffer_.size() == 0) { + return true; + } + // We currently only support one buffer. + gltf_json_.BeginArray("buffers"); + gltf_json_.BeginObject(); + gltf_json_.OutputValue("byteLength", buffer_.size()); + if (!buffer_name_.empty()) { + gltf_json_.OutputValue("uri", buffer_name_); + } + gltf_json_.EndObject(); + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +Status GltfAsset::EncodeExtensionsProperties(EncoderBuffer *buf_out) { + if (draco_compression_used_) { + const std::string draco_tag = "KHR_draco_mesh_compression"; + extensions_used_.insert(draco_tag); + extensions_required_.insert(draco_tag); + } + if (!lights_.empty()) { + extensions_used_.insert("KHR_lights_punctual"); + } + if (!materials_variants_names_.empty()) { + extensions_used_.insert("KHR_materials_variants"); + } + if (!instance_arrays_.empty()) { + extensions_used_.insert("EXT_mesh_gpu_instancing"); + extensions_required_.insert("EXT_mesh_gpu_instancing"); + } + if (mesh_features_used_) { + extensions_used_.insert("EXT_mesh_features"); + } + if (!property_table_schema_.Empty()) { + extensions_used_.insert("EXT_structural_metadata"); + } + + if (!extensions_required_.empty()) { + gltf_json_.BeginArray("extensionsRequired"); + for (const auto &extension : extensions_required_) { + gltf_json_.OutputValue(extension); + } + gltf_json_.EndArray(); + } + if (!extensions_used_.empty()) { + gltf_json_.BeginArray("extensionsUsed"); + for (const auto &extension : extensions_used_) { + gltf_json_.OutputValue(extension); + } + gltf_json_.EndArray(); + } + + const std::string asset_str = gltf_json_.MoveData(); + if (!asset_str.empty()) { + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode extensions."); + } + } + return OkStatus(); +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return BYTE; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_BYTE; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return SHORT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_SHORT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_INT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return FLOAT; +} + +template +int GltfAsset::AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, const std::string &type, + bool compress) { + if (att.size() == 0) { + return -1; // Attribute size must be greater than 0. + } + + std::array value; + std::array min_values; + std::array max_values; + + // Set min and max values. + if (!att.ConvertValue(AttributeValueIndex(0), + &min_values[0])) { + return -1; + } + max_values = min_values; + + if (output_type_ == GltfEncoder::VERBOSE || + att.attribute_type() == GeometryAttribute::POSITION) { + for (AttributeValueIndex i(1); i < static_cast(att.size()); ++i) { + if (!att.ConvertValue(i, &value[0])) { + return -1; + } + for (int j = 0; j < att_components_t; ++j) { + if (value[j] < min_values[j]) { + min_values[j] = value[j]; + } + if (value[j] > max_values[j]) { + max_values[j] = value[j]; + } + } + } + } + + const int kComponentSize = sizeof(att_data_t); + + GltfAccessor accessor; + if (!compress) { + const size_t buffer_start_offset = buffer_.size(); + for (PointIndex v(0); v < num_points; ++v) { + if (!att.ConvertValue(att.mapped_index(v), + &value[0])) { + return -1; + } + + for (int j = 0; j < att_components_t; ++j) { + buffer_.Encode(&value[j], kComponentSize); + } + } + + if (!PadBuffer()) { + return -1; + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + } + + accessor.component_type = GetComponentType(); + accessor.count = num_encoded_points; + if (output_type_ == GltfEncoder::VERBOSE || + att.attribute_type() == GeometryAttribute::POSITION) { + for (int j = 0; j < att_components_t; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + } + accessor.type = type; + accessor.normalized = att.attribute_type() == GeometryAttribute::COLOR && + att.data_type() != DT_FLOAT32; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +const char GltfEncoder::kDracoMetadataGltfAttributeName[] = + "//GLTF/ApplicationSpecificAttributeName"; + +GltfEncoder::GltfEncoder() : out_buffer_(nullptr), output_type_(COMPACT) {} + +template +bool GltfEncoder::EncodeToFile(const T &geometry, const std::string &file_name, + const std::string &base_dir) { + const std::string buffer_name = base_dir + "/buffer0.bin"; + return EncodeFile(geometry, file_name, buffer_name, base_dir).ok(); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename) { + if (filename.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + + std::string dir_path; + std::string basename; + draco::SplitPath(filename, &dir_path, &basename); + const std::string bin_basename = ReplaceFileExtension(basename, "bin"); + const std::string bin_filename = dir_path + "/" + bin_basename; + return EncodeFile(geometry, filename, bin_filename, dir_path); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename) { + if (filename.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + + std::string dir_path; + std::string basename; + draco::SplitPath(filename, &dir_path, &basename); + return EncodeFile(geometry, filename, bin_filename, dir_path); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir) { + if (filename.empty() || bin_filename.empty() || resource_dir.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + const std::string extension = LowercaseFileExtension(filename); + if (extension != "gltf" && extension != "glb") { + return Status(Status::DRACO_ERROR, + "gltf_encoder only supports .gltf or .glb output."); + } + + GltfAsset gltf_asset; + gltf_asset.set_output_type(output_type_); + + if (extension == "gltf") { + std::string bin_path; + std::string bin_basename; + draco::SplitPath(bin_filename, &bin_path, &bin_basename); + gltf_asset.buffer_name(bin_basename); + } else { + gltf_asset.buffer_name(""); + gltf_asset.set_add_images_to_buffer(true); + } + + // Encode the geometry into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(geometry, &gltf_asset, &buffer)); + if (extension == "glb") { + return WriteGlbFile(gltf_asset, buffer, filename); + } + return WriteGltfFiles(gltf_asset, buffer, filename, bin_filename, + resource_dir); +} + +template +Status GltfEncoder::EncodeToBuffer(const T &geometry, + EncoderBuffer *out_buffer) { + GltfAsset gltf_asset; + gltf_asset.set_output_type(output_type_); + gltf_asset.buffer_name(""); + gltf_asset.set_add_images_to_buffer(true); + + // Encode the geometry into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(geometry, &gltf_asset, &buffer)); + + // Define a function for concatenating GLB file chunks into a single buffer. + const auto encode_chunk_to_buffer = + [&out_buffer](const EncoderBuffer &chunk) -> Status { + if (!out_buffer->Encode(chunk.data(), chunk.size())) { + return Status(Status::DRACO_ERROR, "Error writing to buffer."); + } + return OkStatus(); + }; + + // Create GLB file chunks and concatenate them to a single buffer. + return ProcessGlbFileChunks(gltf_asset, buffer, encode_chunk_to_buffer); +} + +// Explicit instantiation for Mesh and Scene. +template bool GltfEncoder::EncodeToFile(const Mesh &geometry, + const std::string &file_name, + const std::string &base_dir); +template bool GltfEncoder::EncodeToFile(const Scene &geometry, + const std::string &file_name, + const std::string &base_dir); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename, + const std::string &bin_filename); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename, + const std::string &bin_filename); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); +template Status GltfEncoder::EncodeToBuffer(const Mesh &geometry, + EncoderBuffer *out_buffer); +template Status GltfEncoder::EncodeToBuffer(const Scene &geometry, + EncoderBuffer *out_buffer); + +Status GltfEncoder::EncodeToBuffer(const Mesh &mesh, GltfAsset *gltf_asset, + EncoderBuffer *out_buffer) { + out_buffer_ = out_buffer; + SetJsonWriterMode(gltf_asset); + if (!gltf_asset->AddDracoMesh(mesh)) { + return Status(Status::DRACO_ERROR, "Error adding Draco mesh."); + } + return gltf_asset->Output(out_buffer); +} + +Status GltfEncoder::EncodeToBuffer(const Scene &scene, GltfAsset *gltf_asset, + EncoderBuffer *out_buffer) { + out_buffer_ = out_buffer; + SetJsonWriterMode(gltf_asset); + DRACO_RETURN_IF_ERROR(gltf_asset->AddScene(scene)); + return gltf_asset->Output(out_buffer); +} + +void GltfEncoder::SetJsonWriterMode(class GltfAsset *gltf_asset) { + if (gltf_asset->output_type() == COMPACT && + gltf_asset->add_images_to_buffer()) { + gltf_asset->set_json_output_mode(JsonWriter::COMPACT); + } else { + gltf_asset->set_json_output_mode(JsonWriter::READABLE); + } +} + +Status GltfEncoder::WriteGltfFiles(const GltfAsset &gltf_asset, + const EncoderBuffer &buffer, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir) { + std::unique_ptr file = + FileWriterFactory::OpenWriter(filename); + if (!file) { + return Status(Status::DRACO_ERROR, "Output glTF file could not be opened."); + } + std::unique_ptr bin_file = + FileWriterFactory::OpenWriter(bin_filename); + if (!bin_file) { + return Status(Status::DRACO_ERROR, + "Output glTF bin file could not be opened."); + } + + // Write the glTF data into the file. + if (!file->Write(buffer.data(), buffer.size())) { + return Status(Status::DRACO_ERROR, "Error writing to glTF file."); + } + + // Write the glTF buffer into the file. + if (!bin_file->Write(gltf_asset.Buffer()->data(), + gltf_asset.Buffer()->size())) { + return Status(Status::DRACO_ERROR, "Error writing to glTF bin file."); + } + + for (int i = 0; i < gltf_asset.NumImages(); ++i) { + const std::string name = resource_dir + "/" + gltf_asset.image_name(i); + const GltfImage *const image = gltf_asset.GetImage(i); + if (!image) { + return Status(Status::DRACO_ERROR, "Error getting glTF image."); + } + DRACO_RETURN_IF_ERROR(WriteTextureToFile(name, *image->texture)); + } + return OkStatus(); +} + +Status GltfEncoder::WriteGlbFile(const GltfAsset &gltf_asset, + const EncoderBuffer &json_data, + const std::string &filename) { + std::unique_ptr file = + FileWriterFactory::OpenWriter(filename); + if (!file) { + return Status(Status::DRACO_ERROR, "Output glb file could not be opened."); + } + + // Define a function for writing GLB file chunks to |file|. + const auto write_chunk_to_file = + [&file](const EncoderBuffer &chunk) -> Status { + if (!file->Write(chunk.data(), chunk.size())) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + return OkStatus(); + }; + + // Create GLB file chunks and write them to file. + return ProcessGlbFileChunks(gltf_asset, json_data, write_chunk_to_file); +} + +Status GltfEncoder::ProcessGlbFileChunks( + const class GltfAsset &gltf_asset, const EncoderBuffer &json_data, + const std::function &process_chunk) const { + // The json data must be padded so the next chunk starts on a 4-byte boundary. + const uint32_t json_pad_length = + (json_data.size() % 4) ? 4 - json_data.size() % 4 : 0; + const uint32_t json_length = json_data.size() + json_pad_length; + const uint32_t total_length = + 12 + 8 + json_length + 8 + gltf_asset.Buffer()->size(); + + EncoderBuffer header; + // Write the glb file header. + const uint32_t gltf_version = 2; + if (!header.Encode("glTF", 4)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(gltf_version)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(total_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + + // Write the JSON chunk. + const uint32_t json_chunk_type = 0x4E4F534A; + if (!header.Encode(json_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(json_chunk_type)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + DRACO_RETURN_IF_ERROR(process_chunk(header)); + DRACO_RETURN_IF_ERROR(process_chunk(json_data)); + + // Pad the data if needed. + header.Clear(); + if (json_pad_length > 0) { + if (!header.Encode(" ", json_pad_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + } + + // Write the binary buffer chunk. + const uint32_t bin_chunk_type = 0x004E4942; + const uint32_t gltf_bin_size = gltf_asset.Buffer()->size(); + if (!header.Encode(gltf_bin_size)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(bin_chunk_type)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + DRACO_RETURN_IF_ERROR(process_chunk(header)); + DRACO_RETURN_IF_ERROR(process_chunk(*gltf_asset.Buffer())); + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.h new file mode 100644 index 000000000..16403ac31 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder.h @@ -0,0 +1,134 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_ENCODER_H_ +#define DRACO_IO_GLTF_ENCODER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/encoder_buffer.h" +#include "draco/io/file_writer_factory.h" +#include "draco/io/file_writer_interface.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Class for encoding draco::Mesh into the glTF file format. +class GltfEncoder { + public: + // Types of output modes for the glTF data encoder. |COMPACT| will output + // required and non-default glTF data. |VERBOSE| will output required and + // default glTF data as well as readable JSON even when the output is saved in + // a glTF-Binary file. + enum OutputType { COMPACT, VERBOSE }; + + GltfEncoder(); + + // Encodes the geometry and saves it into a file. Returns false when either + // the encoding failed or when the file couldn't be opened. + template + bool EncodeToFile(const T &geometry, const std::string &file_name, + const std::string &base_dir); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. The glTF bin file (if needed) will be named stem(|filename|) + + // “.binâ€. The other files (if needed) will be saved to basedir(|filename|). + // If |filename| has the extension "glb" then |filename| will be written as a + // glTF-Binary file. Otherwise |filename| will be written as non-binary glTF + // file. + template + Status EncodeFile(const T &geometry, const std::string &filename); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. |bin_filename| is the name of the glTF bin file. The other + // files (if needed) will be saved to basedir(|filename|). |bin_filename| will + // be ignored if output is glTF-Binary. + template + Status EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. |bin_filename| is the name of the glTF bin file. The other + // files will be saved to |resource_dir|. |bin_filename| and |resource_dir| + // will be ignored if output is glTF-Binary. + template + Status EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); + + // Encodes |geometry| to |out_buffer| in glTF 2.0 GLB format. + template + Status EncodeToBuffer(const T &geometry, EncoderBuffer *out_buffer); + + void set_output_type(OutputType type) { output_type_ = type; } + OutputType output_type() const { return output_type_; } + + // The name of the attribute metadata that contains the glTF attribute + // name. For application-specific generic attributes, if the metadata for + // an attribute contains this key, then the value will be used as the + // encoded attribute name in the output GLTF. + static const char kDracoMetadataGltfAttributeName[]; + + private: + // Encodes the mesh or the point cloud into a buffer. + Status EncodeToBuffer(const Mesh &mesh, class GltfAsset *gltf_asset, + EncoderBuffer *out_buffer); + Status EncodeToBuffer(const Scene &scene, class GltfAsset *gltf_asset, + EncoderBuffer *out_buffer); + + // Sets appropriate Json writer mode based on the provided |gltf_asset| + // options. + static void SetJsonWriterMode(class GltfAsset *gltf_asset); + + // Writes the ".gltf" and associted files. |gltf_asset| holds the glTF data. + // |buffer| is the encoded glTF json data. |filename| is the name of the + // ".gltf" file. |bin_filename| is the name of the glTF bin file. The other + // files will be saved to |resource_dir|. + Status WriteGltfFiles(const class GltfAsset &gltf_asset, + const EncoderBuffer &buffer, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); + + // Writes the ".glb" file. |gltf_asset| holds the glTF data. |json_data| is + // the encoded glTF json data. |filename| is the name of the ".glb" file. + Status WriteGlbFile(const class GltfAsset &gltf_asset, + const EncoderBuffer &json_data, + const std::string &filename); + + // Creates GLB file chunks and passes them to |process_chunk| function for + // processing. |gltf_asset| holds the glTF data. |json_data| is the encoded + // glTF json data. + Status ProcessGlbFileChunks( + const class GltfAsset &gltf_asset, const EncoderBuffer &json_data, + const std::function &process_chunk) const; + + EncoderBuffer *out_buffer_; + OutputType output_type_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_ENCODER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder_test.cc new file mode 100644 index 000000000..179256dfd --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_encoder_test.cc @@ -0,0 +1,1717 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_encoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_reader_factory.h" +#include "draco/io/file_reader_interface.h" +#include "draco/io/file_utils.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_test_helper.h" +#include "draco/io/parser_utils.h" +#include "draco/io/texture_io.h" +#include "draco/material/material_utils.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/mesh_group.h" +#include "draco/scene/scene.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +std::unique_ptr DecodeFullPathGltfFileToScene( + const std::string &file_name) { + GltfDecoder decoder; + + auto maybe_scene = decoder.DecodeFromFileToScene(file_name); + if (!maybe_scene.ok()) { + std::cout << maybe_scene.status().error_msg_string() << std::endl; + return nullptr; + } + std::unique_ptr scene = std::move(maybe_scene).value(); + return scene; +} + +std::unique_ptr DecodeTestGltfFileToScene(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + return DecodeFullPathGltfFileToScene(path); +} +} // namespace + +class GltfEncoderTest : public ::testing::Test { + protected: + // This function searches for the |search| string and checks that there are at + // least |count| occurrences. + void CheckGltfFileAtLeastStringCount(const std::string &gltf_file, + const std::string &search, int count) { + std::vector data; + ASSERT_TRUE(ReadFileToBuffer(gltf_file, &data)); + + draco::DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + + int strings_found = 0; + do { + std::string gltf_line; + draco::parser::ParseLine(&buffer, &gltf_line); + if (gltf_line.empty()) { + break; + } + + if (gltf_line.find(search) != std::string::npos) { + strings_found++; + } + // No need to keep counting pass |count|. + } while (strings_found < count); + ASSERT_GE(strings_found, count); + } + + // This function searches for the |search| string and checks that there no + // occurrences. + void CheckGltfFileNoString(const std::string &gltf_file, + const std::string &search) { + std::vector data; + ASSERT_TRUE(ReadFileToBuffer(gltf_file, &data)); + + draco::DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + + do { + std::string gltf_line; + draco::parser::ParseLine(&buffer, &gltf_line); + if (gltf_line.empty()) { + break; + } + ASSERT_TRUE(gltf_line.find(search) == std::string::npos); + } while (true); + } + + void CheckAnimationAccessors(const Scene &scene, + int expected_num_input_accessors, + int expected_num_output_accessors) { + int num_input_accessors = 0; + int num_output_accessors = 0; + + for (int i = 0; i < scene.NumAnimations(); ++i) { + const Animation *anim = scene.GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + + // The animation accessors in Draco are relative to the Animation object. + // While in glTF the animation accessors are relative to the global + // accessors. + std::unordered_set seen_accessors; + + for (int j = 0; j < anim->NumSamplers(); ++j) { + const AnimationSampler *const sampler = anim->GetSampler(j); + ASSERT_NE(sampler, nullptr); + + if (seen_accessors.find(sampler->input_index) == seen_accessors.end()) { + seen_accessors.insert(sampler->input_index); + num_input_accessors++; + } + if (seen_accessors.find(sampler->output_index) == + seen_accessors.end()) { + seen_accessors.insert(sampler->output_index); + num_output_accessors++; + } + } + } + + EXPECT_EQ(expected_num_input_accessors, num_input_accessors); + EXPECT_EQ(expected_num_output_accessors, num_output_accessors); + } + + void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); + ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); + for (int att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { + const GeometryAttribute::Type att_type = + mesh0->attribute(att_id)->attribute_type(); + const PointAttribute *const att = mesh1->GetNamedAttribute(att_type); + ASSERT_EQ(mesh0->attribute(att_id)->size(), att->size()) + << "Attribute id:" << att_id << " is not equal."; + } + + // Check materials are the same. + if (mesh0->GetMaterialLibrary().NumMaterials() == 0) { + // We add a default material if the source had no materials. + ASSERT_EQ(mesh1->GetMaterialLibrary().NumMaterials(), 1); + } else if (mesh1->GetMaterialLibrary().NumMaterials() == 0) { + // We add a default material if the source had no materials. + ASSERT_EQ(mesh0->GetMaterialLibrary().NumMaterials(), 1); + } else { + ASSERT_EQ(mesh0->GetMaterialLibrary().NumMaterials(), + mesh1->GetMaterialLibrary().NumMaterials()); + for (int i = 0; i < mesh0->GetMaterialLibrary().NumMaterials(); ++i) { + ASSERT_EQ(mesh0->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps(), + mesh1->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps()); + ASSERT_EQ(mesh0->GetMaterialLibrary().GetMaterial(i)->GetName(), + mesh1->GetMaterialLibrary().GetMaterial(i)->GetName()); + } + } + } + + void CompareScenes(const Scene *scene0, const Scene *scene1) { + ASSERT_EQ(scene0->NumMeshes(), scene1->NumMeshes()); + ASSERT_EQ(scene0->NumMeshGroups(), scene1->NumMeshGroups()); + ASSERT_EQ(scene0->NumNodes(), scene1->NumNodes()); + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterials(), + scene1->GetMaterialLibrary().NumMaterials()); + ASSERT_EQ(scene0->NumAnimations(), scene1->NumAnimations()); + ASSERT_EQ(scene0->NumSkins(), scene1->NumSkins()); + ASSERT_EQ(scene0->NumLights(), scene1->NumLights()); + + // Check materials are the same. + for (int i = 0; i < scene0->GetMaterialLibrary().NumMaterials(); ++i) { + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps(), + scene1->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps()); + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterial(i)->GetName(), + scene1->GetMaterialLibrary().GetMaterial(i)->GetName()); + } + + // Check that materials variants names are the same. + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterialsVariants(), + scene1->GetMaterialLibrary().NumMaterialsVariants()); + for (int i = 0; i < scene0->GetMaterialLibrary().NumMaterialsVariants(); + i++) { + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterialsVariantName(i), + scene1->GetMaterialLibrary().GetMaterialsVariantName(i)); + } + + // Check Nodes are the same. + for (draco::SceneNodeIndex i(0); i < scene0->NumNodes(); ++i) { + const SceneNode *const scene_node0 = scene0->GetNode(i); + const SceneNode *const scene_node1 = scene1->GetNode(i); + ASSERT_NE(scene_node0, nullptr); + ASSERT_NE(scene_node1, nullptr); + ASSERT_EQ(scene_node0->GetName(), scene_node1->GetName()); + ASSERT_EQ(scene_node0->GetLightIndex(), scene_node1->GetLightIndex()); + } + + // Check MeshGroups are the same. + for (draco::MeshGroupIndex i(0); i < scene0->NumMeshGroups(); ++i) { + const MeshGroup *const mesh_group0 = scene0->GetMeshGroup(i); + const MeshGroup *const mesh_group1 = scene1->GetMeshGroup(i); + ASSERT_NE(mesh_group0, nullptr); + ASSERT_NE(mesh_group1, nullptr); + ASSERT_EQ(mesh_group0->GetName(), mesh_group1->GetName()); + ASSERT_EQ(mesh_group0->NumMeshInstances(), + mesh_group1->NumMeshInstances()); + + // Check that mesh instanes are the same. + for (int j = 0; j < mesh_group1->NumMeshInstances(); j++) { + const MeshGroup::MeshInstance &instance0 = + mesh_group0->GetMeshInstance(j); + const MeshGroup::MeshInstance &instance1 = + mesh_group1->GetMeshInstance(j); + ASSERT_EQ(instance0.mesh_index, instance1.mesh_index); + ASSERT_EQ(instance0.material_index, instance1.material_index); + ASSERT_EQ(instance0.materials_variants_mappings.size(), + instance1.materials_variants_mappings.size()); + + // Check that materials variants mappings are the same. + for (int k = 0; k < instance0.materials_variants_mappings.size(); k++) { + const MeshGroup::MaterialsVariantsMapping &mapping0 = + instance0.materials_variants_mappings[k]; + const MeshGroup::MaterialsVariantsMapping &mapping1 = + instance1.materials_variants_mappings[k]; + ASSERT_EQ(mapping0.material, mapping1.material); + ASSERT_EQ(mapping0.variants.size(), mapping1.variants.size()); + for (int l = 0; l < mapping0.variants.size(); l++) { + ASSERT_EQ(mapping0.variants[l], mapping1.variants[l]); + } + } + } + } + + // Check Animations are the same. + for (draco::AnimationIndex i(0); i < scene0->NumAnimations(); ++i) { + const Animation *const animation0 = scene0->GetAnimation(i); + const Animation *const animation1 = scene1->GetAnimation(i); + ASSERT_NE(animation0, nullptr); + ASSERT_NE(animation1, nullptr); + ASSERT_EQ(animation0->NumSamplers(), animation1->NumSamplers()); + ASSERT_EQ(animation0->NumChannels(), animation1->NumChannels()); + ASSERT_EQ(animation0->NumNodeAnimationData(), + animation1->NumNodeAnimationData()); + } + + // Check that lights are the same. + for (draco::LightIndex i(0); i < scene0->NumLights(); ++i) { + const Light *const light0 = scene0->GetLight(i); + const Light *const light1 = scene1->GetLight(i); + ASSERT_NE(light0, nullptr); + ASSERT_NE(light1, nullptr); + ASSERT_EQ(light0->GetName(), light1->GetName()); + ASSERT_EQ(light0->GetColor(), light1->GetColor()); + ASSERT_EQ(light0->GetIntensity(), light1->GetIntensity()); + ASSERT_EQ(light0->GetType(), light1->GetType()); + ASSERT_EQ(light0->GetRange(), light1->GetRange()); + if (light0->GetType() == Light::SPOT) { + ASSERT_EQ(light0->GetInnerConeAngle(), light1->GetInnerConeAngle()); + ASSERT_EQ(light0->GetOuterConeAngle(), light1->GetOuterConeAngle()); + } + } + } + + void EncodeMeshToFile(const Mesh &mesh, + const std::string &gltf_file_full_path) { + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE( + gltf_encoder.EncodeToFile(mesh, gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + } + + void EncodeSceneToFile(const Scene &scene, + const std::string &gltf_file_full_path) { + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeToFile(scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + } + + // Encode |mesh| to a temporary glTF file. Then decode the glTF file and + // return the mesh in |mesh_gltf|. + void MeshToDecodedGltfMesh(const Mesh &mesh, + std::unique_ptr *mesh_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + EncodeMeshToFile(mesh, gltf_file_full_path); + *mesh_gltf = std::move(ReadMeshFromFile(gltf_file_full_path)).value(); + ASSERT_NE(*mesh_gltf, nullptr); + } + + // Encode |mesh| to a temporary glTF file. Then decode the glTF file as a + // scene and return it in |scene_gltf|. + void MeshToDecodedGltfScene(const Mesh &mesh, + std::unique_ptr *scene_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + EncodeMeshToFile(mesh, gltf_file_full_path); + *scene_gltf = std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(*scene_gltf, nullptr); + } + + // Encode |scene| to a temporary glTF file. Then decode the glTF file and + // return the scene in |scene_gltf|. + void SceneToDecodedGltfScene(const Scene &scene, + const std::string &temp_basename, + std::unique_ptr *scene_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath(temp_basename); + EncodeSceneToFile(scene, gltf_file_full_path); + + *scene_gltf = DecodeFullPathGltfFileToScene(gltf_file_full_path); + if (SceneUtils::IsDracoCompressionEnabled(scene)) { + // Two occurrences of the Draco compression string is the least amount for + // a valid Draco compressed glTF file. + const std::string khr_draco_compression = "KHR_draco_mesh_compression"; + CheckGltfFileAtLeastStringCount(gltf_file_full_path, + khr_draco_compression, 2); + } + ASSERT_NE((*scene_gltf).get(), nullptr); + } + + void SceneToDecodedGltfScene(const Scene &scene, + std::unique_ptr *scene_gltf) { + SceneToDecodedGltfScene(scene, "test.gltf", scene_gltf); + } + + void EncodeMeshToGltfAndCompare(Mesh *mesh) { + ASSERT_GT(mesh->num_faces(), 0); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + + mesh->DeduplicatePointIds(); + ASSERT_TRUE(mesh->DeduplicateAttributeValues()); + CompareMeshes(mesh, mesh_from_gltf.get()); + } + + void EncodeSceneToGltfAndCompare(Scene *scene) { + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + if (!SceneUtils::IsDracoCompressionEnabled(*scene)) { + CompareScenes(scene, scene_from_gltf.get()); + } + } + + void test_encoding(const std::string &file_name) { + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name, true)); + + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + EncodeMeshToGltfAndCompare(mesh.get()); + } +}; + +TEST_F(GltfEncoderTest, TestGltfEncodingAll) { + // Test decoded mesh from encoded glTF file stays the same. + test_encoding("test_nm.obj.edgebreaker.cl4.2.2.drc"); + test_encoding("cube_att.drc"); + test_encoding("car.drc"); + test_encoding("bunny_gltf.drc"); +} + +TEST_F(GltfEncoderTest, ImportTangentAttribute) { + auto mesh = draco::ReadMeshFromTestFile("sphere.gltf"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const tangent_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::TANGENT); + ASSERT_NE(tangent_att, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_EQ(mesh->num_attributes(), mesh_from_gltf->num_attributes()); +} + +TEST_F(GltfEncoderTest, EncodeColorTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::COLOR, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeColors) { + auto mesh = draco::ReadMeshFromTestFile("test_pos_color.ply"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const color_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::COLOR); + ASSERT_NE(color_att, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + + ASSERT_EQ(mesh->num_faces(), mesh_from_gltf->num_faces()); + ASSERT_EQ(mesh->num_attributes(), mesh_from_gltf->num_attributes()); + ASSERT_EQ( + mesh->NumNamedAttributes(draco::GeometryAttribute::COLOR), + mesh_from_gltf->NumNamedAttributes(draco::GeometryAttribute::COLOR)); +} + +TEST_F(GltfEncoderTest, EncodeNamedGenericAttribute) { + // Load some base mesh. + auto mesh = draco::ReadMeshFromTestFile("test_generic.ply"); + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + int num_vertices = pos_att->size(); + + // Add two new scalar attributes where each value corresponds to the position + // value index (vertex). The first attribute will have metadata, the second + // attribute won't. + std::unique_ptr pa_0(new draco::PointAttribute()); + std::unique_ptr pa_1(new draco::PointAttribute()); + pa_0->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, + draco::DT_FLOAT32, false, + /* one value per position value */ num_vertices); + pa_1->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, + draco::DT_FLOAT32, false, + /* one value per position value */ num_vertices); + + // Set the values for the new attributes. + for (draco::AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const float att_value = avi.value(); + pa_0->SetAttributeValue(avi, &att_value); + pa_1->SetAttributeValue(avi, &att_value); + } + + // Add the attribute to the existing mesh. + const int new_att_id_0 = mesh->AddPerVertexAttribute(std::move(pa_0)); + const int new_att_id_1 = mesh->AddPerVertexAttribute(std::move(pa_1)); + ASSERT_NE(new_att_id_0, -1); + ASSERT_NE(new_att_id_1, -1); + + // Set metadata for first attribute so it gets written out by glTF encoder. + std::unique_ptr am(new draco::AttributeMetadata()); + constexpr char kAttributeName[] = "MyAttributeName"; + constexpr char kDracoMetadataGltfAttributeName[] = + "//GLTF/ApplicationSpecificAttributeName"; + am->AddEntryString(kDracoMetadataGltfAttributeName, kAttributeName); + mesh->AddAttributeMetadata(new_att_id_0, std::move(am)); + + // Make sure the GLTF contains a reference to the named attribute. + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("GenericAttribute.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeToFile(*(mesh.get()), + gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + CheckGltfFileAtLeastStringCount(gltf_file_full_path, kAttributeName, 1); + + // The decoder does not yet support generic attribute names, so instead of + // using the decoder we compare against a golden file. + const std::string gltf_generated_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + std::vector generated_buffer; + ASSERT_TRUE(ReadFileToBuffer(gltf_generated_bin_filename, &generated_buffer)); + std::string generated_str(generated_buffer.data(), generated_buffer.size()); + + const std::string gltf_expected_bin_filename = + GetTestFileFullPath("test_generic_golden.bin"); + const bool kUpdateGoldens = false; + if (kUpdateGoldens) { + ASSERT_TRUE(WriteBufferToFile(generated_buffer.data(), + generated_buffer.size(), + gltf_expected_bin_filename)); + } + std::vector expected_buffer; + ASSERT_TRUE(ReadFileToBuffer(gltf_expected_bin_filename, &expected_buffer)); + std::string expected_str(expected_buffer.data(), expected_buffer.size()); + + EXPECT_TRUE(generated_str == expected_str); +} + +TEST_F(GltfEncoderTest, EncodeMetallicRoughnessTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::METALLIC_ROUGHNESS, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeOcclusionTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::AMBIENT_OCCLUSION, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeEmissiveTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::EMISSIVE, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +// Tests splitting the mesh into multiple primitives. +TEST_F(GltfEncoderTest, EncodeSplitMesh) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(mesh, nullptr); + const int32_t material_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(material_att_id, -1); + EncodeMeshToGltfAndCompare(mesh.get()); +} + +// Tests encoding a scene from a glTF with multiple meshes and primitives, +// including mesh instances. +TEST_F(GltfEncoderTest, EncodeInstancedScene) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "EncodeInstancedScene.gltf", + &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 1); + + const int num_input_accessors = 2; + const int num_output_accessors = 2; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +// Tests encoding a scene from a glTF with multiple meshes and primitives, +// including mesh instances. +TEST_F(GltfEncoderTest, EncodeBoneAnimation) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "EncodeBoneAnimation.gltf", + &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 1); + + const Animation *anim = scene->GetAnimation(AnimationIndex(0)); + ASSERT_NE(anim, nullptr); + ASSERT_TRUE(anim->GetName().empty()); + + // TODO(b/145703399): Figure out how to test that all of the input accessors + // in animation channels in the encoded glTF file will be the same for this + // test file. + const int num_input_accessors = 57; + const int num_output_accessors = 57; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +// Tests encoding a scene from a glTF with nodes that have names. +TEST_F(GltfEncoderTest, EncodeSceneWithNodeNames) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Tests encoding a simple glTF with Draco compression. +TEST_F(GltfEncoderTest, EncodeWithDracoCompression) { + const std::string file_name = "Box/glTF/Box.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, EncodeWeightsJointsWithDracoCompression) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, EncodeTangentsWithDracoCompression) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, TestDracoCompressionWithGeneratedPoints) { + const std::string basename = "test_nm.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(basename); + ASSERT_NE(mesh, nullptr) << "Failed to load " << basename; + + auto maybe_scene = draco::SceneUtils::MeshToScene(std::move(mesh)); + ASSERT_TRUE(maybe_scene.ok()) << "Failed Mesh to Scene conversion."; + const std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, TestDracoCompressionWithDegenerateFaces) { + const std::string basename = "deg_faces.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(basename); + ASSERT_NE(mesh, nullptr) << "Failed to load " << basename; + ASSERT_EQ(mesh->num_faces(), 4); + + auto maybe_scene = draco::SceneUtils::MeshToScene(std::move(mesh)); + ASSERT_TRUE(maybe_scene.ok()) << "Failed Mesh to Scene conversion."; + const std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + const Mesh &scene_first_mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(scene_first_mesh.num_faces(), 4); + + std::unique_ptr scene_from_gltf; + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + const Mesh &scene_from_gltf_first_mesh = + scene_from_gltf->GetMesh(MeshIndex(0)); + ASSERT_EQ(scene_from_gltf_first_mesh.num_faces(), 3); + + CompareScenes(scene.get(), scene_from_gltf.get()); +} + +TEST_F(GltfEncoderTest, DracoCompressionCheckOptions) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const std::string gltf_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + const size_t default_bin_size = draco::GetFileSize(gltf_bin_filename); + + // Test applying more quantization will make the compressed size smaller. + options.quantization_position.SetQuantizationBits(6); + options.quantization_bits_normal = 6; + options.quantization_bits_tex_coord = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t more_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(more_quantization_bin_size, default_bin_size); + + // Test setting more weight quantization then the default makes the compressed + // size smaller. + options.quantization_bits_weight = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t more_weight_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(more_weight_quantization_bin_size, more_quantization_bin_size); + + options.quantization_position.SetQuantizationBits(20); + options.quantization_bits_normal = 20; + options.quantization_bits_tex_coord = 20; + options.quantization_bits_weight = 20; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t less_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(less_quantization_bin_size, default_bin_size); + + DracoCompressionOptions level_options; + level_options.compression_level = 10; // compression level [0-10]. + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t most_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(most_compression_bin_size, default_bin_size); + + level_options.compression_level = 4; + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t less_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(less_compression_bin_size, default_bin_size); + + level_options.compression_level = 0; + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t least_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(least_compression_bin_size, less_compression_bin_size); +} + +TEST_F(GltfEncoderTest, TestQuantizationPerAttribute) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const std::string gltf_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + const size_t default_bin_size = draco::GetFileSize(gltf_bin_filename); + + // Test setting more position quantization then the default makes the + // compressed size smaller. + options.quantization_position.SetQuantizationBits(6); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t position_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(position_quantization_bin_size, default_bin_size); + + // Test setting more normal quantization then the default makes the compressed + // size smaller. + options.quantization_bits_normal = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t normal_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(normal_quantization_bin_size, position_quantization_bin_size); + + // Test setting more tex_coord quantization then the default makes the + // compressed size smaller. + options.quantization_bits_tex_coord = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t tex_coord_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(tex_coord_quantization_bin_size, normal_quantization_bin_size); + + // Test setting more tangent quantization then the default makes the + // compressed size smaller. Weight is tested in DracoCompressionCheckOptions. + options.quantization_bits_tangent = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t tangent_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(tangent_quantization_bin_size, tex_coord_quantization_bin_size); +} + +// Tests encoding a glTF with multiple scaled instances with Draco compression +// using grid options for position quantization. +TEST_F(GltfEncoderTest, TestDracoCompressionWithGridOptions) { + const std::string file_name = + "SpheresScaledInstances/glTF/spheres_scaled_instances.gltf"; + std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const auto bbox = scene->GetMesh(MeshIndex(0)).ComputeBoundingBox(); + const float mesh_size = bbox.Size().MaxCoeff(); + + // All dimensions of the original mesh are between [-1, 1]. Let's move the + // mesh to [0, 2] which will allow us to match grid quantization with the + // regular quantization (grid quantization is always aligned with 0). + Mesh &mesh = scene->GetMesh(MeshIndex(0)); + PointAttribute *pos_att = + mesh.attribute(mesh.GetNamedAttributeId(GeometryAttribute::POSITION)); + for (AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + Vector3f pos; + pos_att->GetValue(avi, &pos[0]); + pos += Vector3f(1.f, 1.f, 1.f); + pos_att->SetAttributeValue(avi, &pos[0]); + } + + DracoCompressionOptions options; + + // First quantize the scene with 8 bits and save the result. + options.quantization_position.SetQuantizationBits(8); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + const std::string gltf_filename = draco::GetTestTempFileFullPath("temp.glb"); + GltfEncoder encoder; + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + // Get the size of the generated file. + const size_t qb_file_size = draco::GetFileSize(gltf_filename); + + // Now set grid quantization and ensure the encoded file size is about the + // same. The max instance scale is 3 and model size is |mesh_size| so the grid + // scale must account for that. + options.quantization_position.SetGrid(mesh_size * 3. / 255.); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + // Get the size of the generated file. + const size_t grid_file_size = draco::GetFileSize(gltf_filename); + + ASSERT_EQ(grid_file_size, qb_file_size); + + // Now set grid quantization to different settings and ensure the encoded size + // changed. We reduce spacing which should increase the size. + options.quantization_position.SetGrid(mesh_size * 3. / 511.); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + + // Get the size of the generated file. + const size_t grid_file_size_2 = draco::GetFileSize(gltf_filename); + ASSERT_GT(grid_file_size_2, grid_file_size); +} + +TEST_F(GltfEncoderTest, TestOutputType) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const size_t default_gltf_size = draco::GetFileSize(gltf_file_full_path); + + // Test setting VERBOSE output type will increase the size of the gltf file. + gltf_encoder.set_output_type(GltfEncoder::VERBOSE); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t verbose_gltf_size = draco::GetFileSize(gltf_file_full_path); + ASSERT_GT(verbose_gltf_size, default_gltf_size); +} + +// Tests copying the name of the input texture file to the encoded texture file. +TEST_F(GltfEncoderTest, CopyTextureName) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + const Material *material = mesh->GetMaterialLibrary().GetMaterial(0); + ASSERT_NE(material, nullptr); + const Texture *texture = + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), "CesiumMilkTruck"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::PNG); +} + +TEST_F(GltfEncoderTest, EncodeTexCoord1) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("MultiUVTest/glTF/MultiUVTest.gltf"); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ( + mesh_from_gltf->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); + ASSERT_EQ( + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().NumTextures(), + 2); + const std::vector textures = { + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().GetTexture(1)}; + EXPECT_EQ(draco::TextureUtils::GetTargetStem(*textures[0]), "uv0"); + EXPECT_EQ(draco::TextureUtils::GetTargetStem(*textures[1]), "uv1"); + EXPECT_EQ(draco::TextureUtils::GetTargetFormat(*textures[0]), + draco::ImageFormat::PNG); + EXPECT_EQ(draco::TextureUtils::GetTargetFormat(*textures[1]), + draco::ImageFormat::PNG); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::TEX_COORD), + 2); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::POSITION), 1); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::TANGENT), 1); +} + +TEST_F(GltfEncoderTest, TestEncodeFileFunctions) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Test encoding with only the gltf filename parameter will output the correct + // bin filename and the textures will be in the same directory as the output + // glTF file. + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("encoded_example.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); + const std::string output_png_filename = + draco::GetTestTempFileFullPath("sphere_Texture0_Normal.png"); + const size_t output_png_size = draco::GetFileSize(output_png_filename); + ASSERT_GT(output_png_size, 0); + + // Test encoding with the gltf and bin filename parameter, the textures will + // be in the same directory as the output glTF file. + const std::string new_bin_filename = + draco::GetTestTempFileFullPath("different_stem_name.bin"); + ASSERT_TRUE( + gltf_encoder + .EncodeFile(*scene, output_gltf_filename, new_bin_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const size_t new_bin_size = draco::GetFileSize(new_bin_filename); + ASSERT_GT(new_bin_size, 0); + ASSERT_EQ(new_bin_size, output_bin_size); + + // Test encoding with the gltf and bin filename and resource_dir parameter, + // the textures will be in the resource_dir directory. + const std::string new_resource_dir = output_gltf_dir + "/textures"; + ASSERT_TRUE(gltf_encoder + .EncodeFile(*scene, output_gltf_filename, + new_bin_filename, new_resource_dir) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const std::string new_png_filename = + draco::GetTestTempFileFullPath("textures/sphere_Texture0_Normal.png"); + const size_t newest_bin_size = draco::GetFileSize(new_bin_filename); + ASSERT_GT(new_bin_size, 0); + ASSERT_EQ(new_bin_size, output_bin_size); + ASSERT_EQ(newest_bin_size, new_bin_size); + const size_t new_png_size = draco::GetFileSize(new_png_filename); + ASSERT_GT(new_png_size, 0); + ASSERT_EQ(new_png_size, output_png_size); +} + +TEST_F(GltfEncoderTest, DoubleSidedMaterial) { + const std::string file_name = "TwoSidedPlane/glTF/TwoSidedPlane.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + EXPECT_EQ(scene_from_gltf->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ( + scene_from_gltf->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), + true); +} + +TEST_F(GltfEncoderTest, EncodeGlb) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, "temp.gltf", &scene_from_gltf); + + std::unique_ptr scene_from_glb; + SceneToDecodedGltfScene(*scene, "temp.glb", &scene_from_glb); + + CompareScenes(scene_from_gltf.get(), scene_from_glb.get()); +} + +TEST_F(GltfEncoderTest, EncodeVertexColor) { + const std::string file_name = "VertexColorTest/glTF/VertexColorTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EXPECT_EQ(scene->NumMeshes(), 2); + const Mesh &mesh = scene->GetMesh(MeshIndex(1)); + EXPECT_EQ(mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, "temp.gltf", &scene_from_gltf); + EXPECT_EQ(scene_from_gltf->NumMeshes(), 2); + const Mesh &encoded_mesh = scene_from_gltf->GetMesh(MeshIndex(1)); + EXPECT_EQ(encoded_mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); +} + +TEST_F(GltfEncoderTest, InterpolationTest) { + const std::string file_name = "InterpolationTest/glTF/InterpolationTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "InterpolationTest.gltf", &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 9); + + const std::vector animation_names{ + "Step Scale", "Linear Scale", + "CubicSpline Scale", "Step Rotation", + "CubicSpline Rotation", "Linear Rotation", + "Step Translation", "CubicSpline Translation", + "Linear Translation"}; + for (int i = 0; i < scene->NumAnimations(); ++i) { + const Animation *const anim = scene->GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + ASSERT_EQ(anim->GetName(), animation_names[i]); + } + + // Currently all animation data is unique. See b/145703399. + const int num_input_accessors = 9; + const int num_output_accessors = 9; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +TEST_F(GltfEncoderTest, KhrMaterialUnlit) { + const std::string filename = + "KhronosSampleModels/UnlitTest/glTF/UnlitTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have four occurences of "KHR_materials_unlit". Two in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 4); +} + +TEST_F(GltfEncoderTest, OneMaterialUnlitWithFallback) { + const std::string filename = + "UnlitWithFallback/one_material_all_fallback/" + "one_material_all_fallback.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have two occurences of "KHR_materials_unlit". One in the + // materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 2); + + // The glTF file should provide a fallback to "KHR_materials_unlit", so there + // should be no "extensionsRequired" element. + CheckGltfFileNoString(output_gltf_filename, "extensionsRequired"); +} + +TEST_F(GltfEncoderTest, MultipleMaterialsUnlitWithFallback) { + std::string filename = + "UnlitWithFallback/three_materials_all_fallback/" + "three_materials_all_fallback.gltf"; + const std::unique_ptr scene_all_fallback( + DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene_all_fallback, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE( + gltf_encoder.EncodeFile(*scene_all_fallback, output_gltf_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have four occurences of "KHR_materials_unlit". Three in + // the materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 4); + + // The glTF file should provide a fallback to "KHR_materials_unlit", so there + // should be no "extensionsRequired" element. + CheckGltfFileNoString(output_gltf_filename, "extensionsRequired"); + + filename = + "UnlitWithFallback/three_materials_one_fallback/" + "three_materials_one_fallback.gltf"; + const std::unique_ptr scene_one_fallback( + DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene_one_fallback, nullptr); + + ASSERT_TRUE( + gltf_encoder.EncodeFile(*scene_one_fallback, output_gltf_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have three occurences of "KHR_materials_unlit". One in the + // materials, one in extensionsUsed, and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 3); + + // The glTF file only has one material with a fallback for + // "KHR_materials_unlit". The other two materials have "KHR_materials_unlit" + // set without a fallback, so there should be an "extensionsRequired" element. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "extensionsRequired", + 1); +} + +TEST_F(GltfEncoderTest, KhrMaterialsSheenExtension) { + const std::string filename = + "KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string out_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(out_filename, &output_gltf_dir, &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, out_filename).ok()) + << "Failed to encode glTF filename:" << out_filename; + + // The "KHR_materials_sheen" should be in material and in extensionsUsed. + CheckGltfFileAtLeastStringCount(out_filename, "KHR_materials_sheen", 2); + CheckGltfFileAtLeastStringCount(out_filename, "sheenColorFactor", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenColorTexture", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenRoughnessFactor", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenRoughnessTexture", 1); +} + +TEST_F(GltfEncoderTest, PbrNextExtensions) { + // Check that a model with PBR material extensions is encoded correctly. This + // is done by encoding an original model with all PBR material extension + // properties and textures, then decoding it and checking that it matches the + // original model. + // TODO(vytyaz): Test multiple materials with various sets of extensions. + + // Read the original model. + const std::string orig_name = "pbr_next/sphere/glTF/sphere.gltf"; + const std::unique_ptr original(DecodeTestGltfFileToScene(orig_name)); + ASSERT_NE(original, nullptr); + const Material &original_mat = *original->GetMaterialLibrary().GetMaterial(0); + + // Check that the original material has PBR extensions. + EXPECT_TRUE(original_mat.HasSheen()); + EXPECT_TRUE(original_mat.HasTransmission()); + EXPECT_TRUE(original_mat.HasClearcoat()); + EXPECT_TRUE(original_mat.HasVolume()); + EXPECT_TRUE(original_mat.HasIor()); + EXPECT_TRUE(original_mat.HasSpecular()); + + // Write the original model to a temporary file. + GltfEncoder encoder; + const std::string tmp_name = draco::GetTestTempFileFullPath("tmp.gltf"); + DRACO_ASSERT_OK(encoder.EncodeFile(*original, tmp_name)); + + // Read model from the temporay file. + GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto encoded, decoder.DecodeFromFileToScene(tmp_name)); + ASSERT_NE(encoded, nullptr); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithoutFallback) { + // This is the example from Khronos, which should have "KHR_texture_transform" + // listed in the extensionsRequired, but does not for testing out client + // implementations. + const std::string filename = + "KhronosSampleModels/TextureTransformTest/glTF/TextureTransformTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have eight occurences of "KHR_materials_unlit". Six in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 8); + + // glTF file should still contain only two occurences of '"sampler": 0'. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "\"sampler\": 0", 2); + + // glTF file should have one occurence of "wrapS", "wrapT", "minFilter", and + // "magFilter". + CheckGltfFileAtLeastStringCount(output_gltf_filename, "wrapS", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "wrapT", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "minFilter", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "magFilter", 1); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithoutFallbackRequried) { + // This is the example from Khronos, changed to list "KHR_texture_transform" + // in extensionsRequired. + const std::string filename = + "glTF/TextureTransformTestWithRequired/" + "TextureTransformTestWithRequired.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have eight occurences of "KHR_materials_unlit". Six in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 8); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithFallback) { + // This is an example of "KHR_texture_transform" extension with fallback data. + const std::string filename = + "glTF/KhrTextureTransformWithFallback/" + "KhrTextureTransformWithFallback.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have two occurences of "KHR_materials_unlit". One in the + // materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 2); +} + +// Tests if the source file has a node with an identity matrix, that we do not +// output the identiy matrix. +TEST_F(GltfEncoderTest, MeshWithIdentityTransformation) { + const std::string gltf_source_full_path = + GetTestFileFullPath("Triangle/glTF/Triangle_identity_matrix.gltf"); + + // Check that the source file contains one "matrix" and no "translation" + // strings. + CheckGltfFileAtLeastStringCount(gltf_source_full_path, "matrix", 1); + CheckGltfFileNoString(gltf_source_full_path, "translation"); + + std::unique_ptr scene = draco::ReadSceneFromTestFile( + "Triangle/glTF/Triangle_identity_matrix.gltf"); + ASSERT_NE(scene, nullptr); + SceneNode *scene_node = scene->GetNode(SceneNodeIndex(0)); + ASSERT_NE(scene_node, nullptr); + const TrsMatrix &trs_matrix = scene_node->GetTrsMatrix(); + + // gltf_decoder will not set the trs matrix if the matrix is identity. + ASSERT_FALSE(trs_matrix.MatrixSet()); + + // Add the identity matrix. + TrsMatrix trsm; + trsm.SetMatrix(Eigen::Matrix4d::Identity()); + scene_node->SetTrsMatrix(trsm); + + const TrsMatrix &check_trs_matrix = scene_node->GetTrsMatrix(); + ASSERT_TRUE(check_trs_matrix.MatrixSet()); + ASSERT_EQ(check_trs_matrix.IsMatrixIdentity(), true); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("MeshWithIdentityTransformation.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile( + *scene.get(), gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + std::unique_ptr scene_gltf = + std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(scene_gltf, nullptr); + // Check that the output file contains no "matrix" or "translation" strings. + CheckGltfFileNoString(gltf_file_full_path, "matrix"); + CheckGltfFileNoString(gltf_file_full_path, "translation"); +} + +// Tests if the source file has a node with a matrix that only has the +// translation values set. If it does then instead of outputting the full matrix +// we only output the "translation" glTF element. +TEST_F(GltfEncoderTest, MeshWithTranslationOnlyMatrix) { + std::unique_ptr scene = draco::ReadSceneFromTestFile( + "Triangle/glTF/Triangle_translation_only_matrix.gltf"); + ASSERT_NE(scene, nullptr); + SceneNode *scene_node = scene->GetNode(SceneNodeIndex(0)); + ASSERT_NE(scene_node, nullptr); + const TrsMatrix &input_trs_matrix = scene_node->GetTrsMatrix(); + ASSERT_TRUE(input_trs_matrix.MatrixSet()); + ASSERT_FALSE(input_trs_matrix.TranslationSet()); + ASSERT_FALSE(input_trs_matrix.RotationSet()); + ASSERT_FALSE(input_trs_matrix.ScaleSet()); + ASSERT_TRUE(input_trs_matrix.IsMatrixTranslationOnly()); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("MeshWithTranslationOnlyMatrix.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile( + *scene.get(), gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + std::unique_ptr scene_gltf = + std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(scene_gltf, nullptr); + SceneNode *output_scene_node = scene_gltf->GetNode(SceneNodeIndex(0)); + ASSERT_NE(output_scene_node, nullptr); + const TrsMatrix &output_trs_matrix = output_scene_node->GetTrsMatrix(); + ASSERT_FALSE(output_trs_matrix.MatrixSet()); + ASSERT_TRUE(output_trs_matrix.TranslationSet()); + ASSERT_FALSE(output_trs_matrix.RotationSet()); + ASSERT_FALSE(output_trs_matrix.ScaleSet()); +} + +// Tests that a scene can be encoded to buffer in GLB format. +TEST_F(GltfEncoderTest, EncodeToBuffer) { + // Load scene from file. + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + + // Encode scene to buffer in GLB format. + GltfEncoder encoder; + EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(*scene, &buffer)); + ASSERT_NE(buffer.size(), 0); + + // Write scene to file in GLB format. + const std::string glb_file_path = draco::GetTestTempFileFullPath("temp.glb"); + std::string folder_path; + std::string glb_file_name; + draco::SplitPath(glb_file_path, &folder_path, &glb_file_name); + encoder.EncodeToFile(*scene, glb_file_path, folder_path); + + // Check that the buffer contents match the GLB file contents. + ASSERT_EQ(buffer.size(), draco::GetFileSize(glb_file_path)); + std::vector file_data; + ASSERT_TRUE(ReadFileToBuffer(glb_file_path, &file_data)); + ASSERT_EQ(std::memcmp(file_data.data(), buffer.data(), buffer.size()), 0); +} + +// Tests that a scene with lights can be encoded into a file. +TEST_F(GltfEncoderTest, EncodeLights) { + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumLights(), 4); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Helper method for adding mesh group GPU instancing to the milk truck scene. +draco::Status AddGpuInstancingToMilkTruck(draco::Scene *scene) { + // Create an instance and set its transformation TRS vectors. + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(Eigen::Vector3d(-0.2, 0.0, 0.0)); + instance_0.trs.SetScale(Eigen::Vector3d(1.0, 1.0, 1.0)); + + // Create another instance. + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(Eigen::Vector3d(1.0, 0.0, 0.0)); + instance_1.trs.SetScale(Eigen::Vector3d(2.0, 2.0, 2.0)); + + // Add an empty GPU instancing object to the scene. + const draco::InstanceArrayIndex index = scene->AddInstanceArray(); + draco::InstanceArray *gpu_instancing = scene->GetInstanceArray(index); + + // Add two instances to the GPU instancing object stored in the scene. + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_0)); + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_1)); + + // Assign the GPU instancing object to two mesh groups in two scene nodes. + scene->GetNode(draco::SceneNodeIndex(2))->SetInstanceArrayIndex(index); + scene->GetNode(draco::SceneNodeIndex(4))->SetInstanceArrayIndex(index); + + return draco::OkStatus(); +} + +// Tests that a scene with instance arrays can be encoded into a file. Decoder +// has no GPU instancing support, so we will compare encoded file to a golden +// file. +TEST_F(GltfEncoderTest, EncodeInstanceArrays) { + // Read the milk truck. + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Add GPU instancing to the scene for testing. + DRACO_ASSERT_OK(AddGpuInstancingToMilkTruck(scene.get())); + ASSERT_EQ(scene->NumInstanceArrays(), 1); + ASSERT_EQ(scene->NumNodes(), 5); + + // Prepare file paths. + const std::string temp_path = draco::GetTestTempFileFullPath("Truck.glb"); + const std::string golden_path = + GetTestFileFullPath("CesiumRowingTruckWithGpuInstancing.glb"); + + // Encode scene to a temporary file in GLB format. + std::string folder; + std::string name; + draco::SplitPath(temp_path, &folder, &name); + GltfEncoder encoder; + ASSERT_TRUE(encoder.EncodeToFile(*scene, temp_path, folder)) + << "Failed to encode to temporary file:" << temp_path; + + // Read encoded file to buffer. + std::vector encoded_data; + ASSERT_TRUE(ReadFileToBuffer(temp_path, &encoded_data)); +} + +// Tests that a scene with materials variants can be encoded into a file. +TEST_F(GltfEncoderTest, EncodeMaterialsVariants) { + const std::string file_name = + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterialsVariants(), 2); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Tests encoding of draco::Scene to glTF with various mesh feature ID sets and +// structural metadata property table. +TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithStructuralMetadata) { + const std::string file_name = "BoxMeta/glTF/BoxMeta.gltf"; + constexpr bool kHasMeshFeatures = true; + constexpr bool kHasStructuralMetadata = true; + constexpr bool kHasDracoCompression = false; + + // Read test file from file. + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Encode the scene to glTF and decode it back to draco::Scene and check. + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, + kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene_from_gltf); +} + +// Tests encoding of draco::Scene with Draco compression to glTF with various +// mesh feature ID sets. +TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithDracoCompression) { + const std::string file_name = "BoxMetaDraco/glTF/BoxMetaDraco.gltf"; + constexpr bool kHasMeshFeatures = true; + constexpr bool kHasStructuralMetadata = false; + constexpr bool kHasDracoCompression = true; + + // Read test file from file. + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Encode the scene to glTF and decode it back to draco::Scene and check. + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, + kHasDracoCompression); +} + +// Tests encoding of draco::Mesh to glTF with various mesh feature ID sets and +// structural metadata property table. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithStructuralMetadata) { + const std::string file_name = "BoxMeta/glTF/BoxMeta.gltf"; + constexpr bool kHasDracoCompression = false; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, + kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh_from_gltf); +} + +// Tests encoding of draco::Mesh with Draco compression to glTF with various +// mesh feature ID sets. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithDracoCompression) { + constexpr bool kHasDracoCompression = true; + const std::string file_name = "BoxMetaDraco/glTF/BoxMetaDraco.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, + kHasDracoCompression); +} + +// Tests encoding of draco::Mesh with mesh features associated with different +// mesh primitives. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithMultiplePrimitives) { + const std::string file_name = "BoxesMeta/glTF/BoxesMeta.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + // All mesh features should share two textures. + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh_from_gltf->NumMeshFeatures(), 5); + + // First two mesh features should be used by material 0 and the reamining by + // material 1. + for (draco::MeshFeaturesIndex mfi(0); mfi < 5; ++mfi) { + // Each mesh feature should be used by a single material. + ASSERT_EQ(mesh_from_gltf->NumMeshFeaturesMaterialMasks(mfi), 1); + if (mfi.value() < 2) { + ASSERT_EQ(mesh_from_gltf->GetMeshFeaturesMaterialMask(mfi, 0), 0); + } else { + ASSERT_EQ(mesh_from_gltf->GetMeshFeaturesMaterialMask(mfi, 0), 1); + } + } + // All mesh features should share two textures. + ASSERT_EQ(mesh_from_gltf->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Ensure it still works correctly when we re-encode the source |mesh| as a + // scene. + std::unique_ptr scene_from_gltf; + MeshToDecodedGltfScene(*mesh, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + + ASSERT_EQ(scene_from_gltf->NumMeshes(), 2); + + // First mesh should have 2 mesh features and the other one 3 mesh features. + ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(0)).NumMeshFeatures(), 2); + ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(1)).NumMeshFeatures(), 3); + + // All mesh features should share two textures. + ASSERT_EQ(scene_from_gltf->GetNonMaterialTextureLibrary().NumTextures(), 2); +} + +// Tests encoding of draco::Mesh containing a point cloud and two materials. +TEST_F(GltfEncoderTest, EncodePointCloudWithMaterials) { + const std::string file_name = + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Input should have no faces. + ASSERT_EQ(mesh->num_faces(), 0); + + // There should be two materials + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + + // Encode the mesh to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + + ASSERT_EQ(mesh_from_gltf->num_faces(), 0); + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 2); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.cc new file mode 100644 index 000000000..13cce6f4e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.cc @@ -0,0 +1,823 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_test_helper.h" + +#include +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/metadata/property_table.h" +#include "draco/texture/texture_library.h" + +namespace draco { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) { + // Check the scene. + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + TextureLibrary &texture_library = scene->GetNonMaterialTextureLibrary(); + ASSERT_EQ(texture_library.NumTextures(), 0); + + // Check the mesh. + Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(mesh.num_faces(), 12); + ASSERT_EQ(mesh.num_attributes(), 2); + ASSERT_EQ(mesh.num_points(), 24); + + // Get mesh element counts. + const int num_faces = mesh.num_faces(); + const int num_corners = 3 * mesh.num_faces(); + const int num_vertices = + mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size(); + + // Add feature ID set with per-face Uint8 attribute named _FEATURE_ID_0. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_UINT8; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, mesh.num_faces()); + for (AttributeValueIndex avi(0); avi < num_faces; ++avi) { + const int8_t val = avi.value(); + pa->SetAttributeValue(avi, &val); + } + const int att_id = mesh.AddPerFaceAttribute(std::move(pa)); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_0"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetLabel("faces"); + features->SetFeatureCount(num_faces); + features->SetNullFeatureId(100); + features->SetPropertyTableIndex(0); + features->SetAttributeIndex(0); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with per-vertex Uint16 attribute named _FEATURE_ID_1. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_UINT16; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, num_vertices); + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const uint16_t val = avi.value(); + pa->SetAttributeValue(avi, &val); + } + const int att_id = mesh.AddPerVertexAttribute(std::move(pa)); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_1"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetLabel("vertices"); + features->SetFeatureCount(num_vertices); + features->SetNullFeatureId(101); + features->SetPropertyTableIndex(1); + features->SetAttributeIndex(1); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with per-corner Float attribute named _FEATURE_ID_2. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, num_corners); + IndexTypeVector corner_to_value( + num_corners); + for (AttributeValueIndex avi(0); avi < num_corners; ++avi) { + const float val = avi.value(); + pa->SetAttributeValue(avi, &val); + corner_to_value[CornerIndex(avi.value())] = avi; + } + const int att_id = + mesh.AddAttributeWithConnectivity(std::move(pa), corner_to_value); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_2"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetFeatureCount(num_corners); + features->SetAttributeIndex(2); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with the IDs stored in the R texture channel and + // accessible via the first texture coordinate attribute. + { + // Add the first texture coordinate attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::TEX_COORD, 2, kType, false, num_vertices); + std::vector> uv = { + {0.0000f, 0.0000f}, {0.0000f, 0.5000f}, {0.0000f, 1.0000f}, + {0.5000f, 0.0000f}, {0.5000f, 0.5000f}, {0.5000f, 1.0000f}, + {1.0000f, 0.0000f}, {1.0000f, 0.5000f}}; + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const int index = avi.value(); + pa->SetAttributeValue(avi, uv[index].data()); + } + mesh.AddPerVertexAttribute(std::move(pa)); + } + + // Add feature ID set with the IDs stored in the GBA texture channels and + // accessible via the second texture coordinate attribute. + { + // Add the second texture coordinate attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::TEX_COORD, 2, kType, false, num_vertices); + std::vector> uv = { + {0.0000f, 0.0000f}, {0.0000f, 0.5000f}, {0.0000f, 1.0000f}, + {0.5000f, 0.0000f}, {0.5000f, 0.5000f}, {0.5000f, 1.0000f}, + {1.0000f, 0.0000f}, {1.0000f, 0.5000f}}; + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const int index = avi.value(); + pa->SetAttributeValue(avi, uv[index].data()); + } + mesh.AddPerVertexAttribute(std::move(pa)); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + } +} + +void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) { + // Add structural metadata property table schema in the following JSON: + // "schema": { + // "id": "galaxy", + // "classes": { + // "planet": { + // "properties": { + // "color": { + // "componentType": "UINT8", + // "description": "The RGB color.", + // "required": true, + // "type": "VEC3" + // }, + // "name": { + // "description": "The name.", + // "required": true, + // "type": "STRING" + // } + // "sequence": { + // "description": "The number sequence.", + // "required": false, + // "type": "SCALAR" + // } + // } + // } + // }, + // "enums": { + // "classifications": { + // "description": "Classifications of planets.", + // "name": "classifications", + // "values": [ + // { "name": "Unspecified", "value": 0 }, + // { "name": "Gas Giant", "value": 1 }, + // { "name": "Waterworld", "value": 2 }, + // { "name": "Agriworld", "value": 3 }, + // { "name": "Ordnance", "value": 4 } + // ] + // } + // } + // } + typedef PropertyTable::Schema::Object Object; + PropertyTable::Schema schema; + Object &json = schema.json; + json.SetObjects().emplace_back("id", "galaxy"); + json.SetObjects().emplace_back("classes"); + json.SetObjects().back().SetObjects().emplace_back("planet"); + Object &planet = json.SetObjects().back().SetObjects().back(); + planet.SetObjects().emplace_back("properties"); + Object &properties = planet.SetObjects().back(); + + properties.SetObjects().emplace_back("color"); + Object &color = properties.SetObjects().back(); + color.SetObjects().emplace_back("componentType", "UINT8"); + color.SetObjects().emplace_back("description", "The RGB color."); + color.SetObjects().emplace_back("required", true); + color.SetObjects().emplace_back("type", "VEC3"); + + properties.SetObjects().emplace_back("name"); + Object &name = properties.SetObjects().back(); + name.SetObjects().emplace_back("description", "The name."); + name.SetObjects().emplace_back("required", true); + name.SetObjects().emplace_back("type", "STRING"); + + properties.SetObjects().emplace_back("sequence"); + Object &sequence = properties.SetObjects().back(); + sequence.SetObjects().emplace_back("description", "The number sequence."); + sequence.SetObjects().emplace_back("required", false); + sequence.SetObjects().emplace_back("type", "SCALAR"); + + json.SetObjects().emplace_back("enums"); + json.SetObjects().back().SetObjects().emplace_back("classifications"); + Object &classifications = json.SetObjects().back().SetObjects().back(); + classifications.SetObjects().emplace_back("description", + "Classifications of planets."); + classifications.SetObjects().emplace_back("name", "classifications"); + classifications.SetObjects().emplace_back("values"); + Object &values = classifications.SetObjects().back(); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Unspecified"); + values.SetArray().back().SetObjects().emplace_back("value", 0); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Gas Giant"); + values.SetArray().back().SetObjects().emplace_back("value", 1); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Waterworld"); + values.SetArray().back().SetObjects().emplace_back("value", 2); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Agriworld"); + values.SetArray().back().SetObjects().emplace_back("value", 3); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Ordnance"); + values.SetArray().back().SetObjects().emplace_back("value", 4); + + // Add property table schema to the scene. + scene->GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Add structural metadata property table. + std::unique_ptr table(new PropertyTable()); + table->SetName("Galaxy far far away."); + table->SetClass("planet"); + table->SetCount(16); + + // Add property describing RGB color components of the planet class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("color"); + property->GetData().target = 34962; // ARRAY_BUFFER. + property->GetData().data = {94, 94, 194, // Tatooine + 94, 145, 161, // Corusant + 118, 171, 91, // Naboo + 103, 139, 178, // Alderaan + 83, 98, 154, // Dagobah + 91, 177, 175, // Mandalore + 190, 92, 108, // Corellia + 72, 69, 169, // Kamino + 154, 90, 101, // Kashyyyk + 174, 85, 175, // Dantooine + 184, 129, 96, // Hoth + 185, 91, 180, // Mustafar + 194, 150, 83, // Bespin + 204, 111, 134, // Yavin + 182, 90, 89, // Geonosis + 0, 0, 0}; // UNLABELED + table->AddProperty(std::move(property)); + } + + // Add property that describes names of the planet class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("name"); + property->GetData().target = 34963; // ELEMENT_ARRAY_BUFFER. + const std::string data = + "named_class:Tatooine" + "named_class:Corusant" + "named_class:Naboo" + "named_class:Alderaan" + "named_class:Dagobah" + "named_class:Mandalore" + "named_class:Corellia" + "named_class:Kamino" + "named_class:Kashyyyk" + "named_class:Dantooine" + "named_class:Hoth" + "named_class:Mustafar" + "named_class:Bespin" + "named_class:Yavin" + "named_class:Geonosis" + "UNLABELED"; + property->GetData().data.assign(data.begin(), data.end()); + property->GetStringOffsets().type = "UINT32"; + property->GetStringOffsets().data.target = 34963; // ELEMENT_ARRAY_BUFFER. + property->GetStringOffsets().data.data = {0, 0, 0, 0, // Tatooine + 20, 0, 0, 0, // Corusant + 40, 0, 0, 0, // Naboo + 57, 0, 0, 0, // Alderaan + 77, 0, 0, 0, // Dagobah + 96, 0, 0, 0, // Mandalore + 117, 0, 0, 0, // Corellia + 137, 0, 0, 0, // Kamino + 155, 0, 0, 0, // Kashyyyk + 175, 0, 0, 0, // Dantooine + 196, 0, 0, 0, // Hoth + 212, 0, 0, 0, // Mustafar + 232, 0, 0, 0, // Bespin + 250, 0, 0, 0, // Yavin + 12, 1, 0, 0, // Geonosis + 32, 1, 0, 0, // UNLABELED + 41, 1, 0, 0}; + table->AddProperty(std::move(property)); + } + + // Add property that contains variable-length number sequence of the planet + // class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("sequence"); + property->GetData().target = 34963; // ELEMENT_ARRAY_BUFFER. + const std::vector data = { + 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, // Tatooine + 6.5f, 7.5f, // Corusant + 8.5f, // Naboo + 9.5f, // Alderaan + 10.5f, 11.5f, // Dagobah + 12.5f, 13.5f, 14.5f, 15.5f, // Mandalore + 16.5f, 17.5f, // Corellia + 18.5f, 19.5f, // Kamino + 20.5f, 21.5f, 22.5f, // Kashyyyk + 23.5f, 24.5f, 25.5f, // Dantooine + 26.5f, 27.5f, // Hoth + 28.5f, 29.5f, // Mustafar + 30.5f, 31.5f, 32.5f, // Bespin + 33.5f, 34.5f, 35.5f, // Yavin + 36.5f, 37.5f, 38.5f, 39.5f, 40.5f // Geonosis + }; // UNLABELED (empty array). + property->GetData().data.resize(4 * data.size()); + memcpy(property->GetData().data.data(), data.data(), 4 * data.size()); + property->GetArrayOffsets().type = "UINT8"; + property->GetArrayOffsets().data.target = 34963; // ELEMENT_ARRAY_BUFFER. + property->GetArrayOffsets().data.data = { + 0 * 4, // Tatooine + 6 * 4, // Corusant + 8 * 4, // Naboo + 9 * 4, // Alderaan + 10 * 4, // Dagobah + 12 * 4, // Mandalore + 16 * 4, // Corellia + 18 * 4, // Kamino + 20 * 4, // Kashyyyk + 23 * 4, // Dantooine + 26 * 4, // Hoth + 28 * 4, // Mustafar + 30 * 4, // Bespin + 33 * 4, // Yavin + 36 * 4, // Geonosis + 41 * 4, // UNLABELED (empty array). + 41 * 4}; + table->AddProperty(std::move(property)); + } + + // Add property table to the scene. + scene->GetStructuralMetadata().AddPropertyTable(std::move(table)); +} + +template <> +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &geometry, + bool has_draco_compression) { + CheckBoxMetaMeshFeatures(geometry, geometry.GetNonMaterialTextureLibrary(), + has_draco_compression); +} + +template <> +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Scene &geometry, + bool has_draco_compression) { + ASSERT_EQ(geometry.NumMeshes(), 1); + CheckBoxMetaMeshFeatures(geometry.GetMesh(MeshIndex(0)), + geometry.GetNonMaterialTextureLibrary(), + has_draco_compression); +} + +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh, + const TextureLibrary &texture_lib, + bool has_draco_compression) { + // Check texture library. + ASSERT_EQ(texture_lib.NumTextures(), 2); + + // Check basic mesh properties. + ASSERT_EQ(mesh.NumMeshFeatures(), 5); + ASSERT_EQ(mesh.num_faces(), 12); + ASSERT_EQ(mesh.num_attributes(), 7); + ASSERT_EQ(mesh.num_points(), 36); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::GENERIC), 3); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + + // Get mesh element counts. + const int num_faces = mesh.num_faces(); + const int num_corners = 3 * mesh.num_faces(); + const int num_vertices = + mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size(); + + // Check mesh feature ID set at index 0. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(0)); + ASSERT_EQ(features.GetLabel(), "faces"); + ASSERT_EQ(features.GetFeatureCount(), num_faces); + ASSERT_EQ(features.GetNullFeatureId(), 100); + ASSERT_EQ(features.GetPropertyTableIndex(), 0); + ASSERT_EQ(features.GetAttributeIndex(), 0); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-face Uint8 attribute named _FEATURE_ID_0. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_0"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_UINT8); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_faces); + ASSERT_EQ(att->indices_map_size(), num_corners); + + // Check that the values are all the numbers from 0 to 12. + const std::vector expected_values = + has_draco_compression + ? std::vector{7, 11, 10, 3, 2, 5, 4, 1, 6, 9, 8, 0} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + for (int i = 0; i < num_faces; i++) { + uint8_t val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners of each face have a common value. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + ASSERT_EQ(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[1])); + ASSERT_EQ(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[2])); + } + } + + // Check the 2nd mesh feature ID set at index 1. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(1)); + ASSERT_EQ(features.GetLabel(), "vertices"); + ASSERT_EQ(features.GetFeatureCount(), num_vertices); + ASSERT_EQ(features.GetNullFeatureId(), 101); + ASSERT_EQ(features.GetPropertyTableIndex(), 1); + ASSERT_EQ(features.GetAttributeIndex(), 1); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-vertex Uint16 attribute named _FEATURE_ID_1. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_1"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_UINT16); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_vertices); + ASSERT_EQ(att->indices_map_size(), num_corners); + + // Check that the values are all the numbers from 0 to 7. + const std::vector expected_values = + has_draco_compression ? std::vector{3, 6, 7, 4, 5, 0, 1, 2} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7}; + for (int i = 0; i < num_vertices; i++) { + uint16_t val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners of a face have unique values. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[1])); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[1]), + *att->GetAddressOfMappedIndex(face[2])); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[2]), + *att->GetAddressOfMappedIndex(face[0])); + } + } + + // Check the 3rd mesh feature ID set at index 2. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(2)); + ASSERT_TRUE(features.GetLabel().empty()); + ASSERT_EQ(features.GetFeatureCount(), num_corners); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), 2); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-corner Float attribute named _FEATURE_ID_2. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_2"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_FLOAT32); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_corners); + ASSERT_EQ(att->indices_map_size(), 0); + ASSERT_TRUE(att->is_mapping_identity()); + + // Check that the values are from 0 to 35. + const std::vector expected_values = + has_draco_compression + ? std::vector{23, 21, 22, 33, 34, 35, 31, 32, 30, 9, 10, 11, + 7, 8, 6, 15, 16, 17, 14, 12, 13, 5, 3, 4, + 19, 20, 18, 27, 28, 29, 26, 24, 25, 1, 2, 0} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35}; + for (int i = 0; i < num_corners; i++) { + float val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners have unique values. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + float v0, v1, v2; + att->GetMappedValue(face[0], &v0); + att->GetMappedValue(face[1], &v1); + att->GetMappedValue(face[2], &v2); + ASSERT_EQ(v0, expected_values[3 * i + 0]); + ASSERT_EQ(v1, expected_values[3 * i + 1]); + ASSERT_EQ(v2, expected_values[3 * i + 2]); + } + } + + // Check mesh feature ID set at index 3. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(3)); + ASSERT_TRUE(features.GetLabel().empty()); + ASSERT_EQ(features.GetFeatureCount(), 6); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), -1); + } + + // Check mesh feature ID set at index 4. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(4)); + ASSERT_EQ(features.GetLabel(), "water"); + ASSERT_EQ(features.GetFeatureCount(), 2); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), -1); + } +} + +void GltfTestHelper::CheckBoxMetaStructuralMetadata( + const StructuralMetadata &structural_metadata) { + // Check property table schema. + { + const PropertyTable::Schema &schema = + structural_metadata.GetPropertyTableSchema(); + ASSERT_FALSE(schema.Empty()); + const PropertyTable::Schema::Object &json = schema.json; + ASSERT_EQ(json.GetObjects().size(), 3); + ASSERT_EQ(json.GetObjects()[0].GetName(), "classes"); + ASSERT_EQ(json.GetObjects()[0].GetObjects().size(), 1); + ASSERT_EQ(json.GetObjects()[0].GetObjects()[0].GetName(), "planet"); + ASSERT_EQ(json.GetObjects()[0].GetObjects()[0].GetObjects().size(), 1); + + const auto &properties = + json.GetObjects()[0].GetObjects()[0].GetObjects()[0]; + ASSERT_EQ(properties.GetName(), "properties"); + ASSERT_EQ(properties.GetObjects().size(), 3); + + const auto &color = properties.GetObjects()[0]; + ASSERT_EQ(color.GetName(), "color"); + ASSERT_EQ(color.GetObjects().size(), 4); + ASSERT_EQ(color.GetObjects()[0].GetName(), "componentType"); + ASSERT_EQ(color.GetObjects()[1].GetName(), "description"); + ASSERT_EQ(color.GetObjects()[2].GetName(), "required"); + ASSERT_EQ(color.GetObjects()[3].GetName(), "type"); + ASSERT_EQ(color.GetObjects()[0].GetString(), "UINT8"); + ASSERT_EQ(color.GetObjects()[1].GetString(), "The RGB color."); + ASSERT_TRUE(color.GetObjects()[2].GetBoolean()); + ASSERT_EQ(color.GetObjects()[3].GetString(), "VEC3"); + + const auto &name = properties.GetObjects()[1]; + ASSERT_EQ(name.GetName(), "name"); + ASSERT_EQ(name.GetObjects().size(), 3); + ASSERT_EQ(name.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(name.GetObjects()[1].GetName(), "required"); + ASSERT_EQ(name.GetObjects()[2].GetName(), "type"); + ASSERT_EQ(name.GetObjects()[0].GetString(), "The name."); + ASSERT_TRUE(name.GetObjects()[1].GetBoolean()); + ASSERT_EQ(name.GetObjects()[2].GetString(), "STRING"); + + const auto &sequence = properties.GetObjects()[2]; + ASSERT_EQ(sequence.GetName(), "sequence"); + ASSERT_EQ(sequence.GetObjects().size(), 3); + ASSERT_EQ(sequence.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(sequence.GetObjects()[1].GetName(), "required"); + ASSERT_EQ(sequence.GetObjects()[2].GetName(), "type"); + ASSERT_EQ(sequence.GetObjects()[0].GetString(), "The number sequence."); + ASSERT_FALSE(sequence.GetObjects()[1].GetBoolean()); + ASSERT_EQ(sequence.GetObjects()[2].GetString(), "SCALAR"); + + ASSERT_EQ(json.GetObjects()[1].GetName(), "enums"); + const auto &classifications = json.GetObjects()[1].GetObjects()[0]; + ASSERT_EQ(classifications.GetName(), "classifications"); + ASSERT_EQ(classifications.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(classifications.GetObjects()[0].GetString(), + "Classifications of planets."); + ASSERT_EQ(classifications.GetObjects()[1].GetName(), "name"); + ASSERT_EQ(classifications.GetObjects()[1].GetString(), "classifications"); + ASSERT_EQ(classifications.GetObjects()[2].GetName(), "values"); + const auto &values = classifications.GetObjects()[2]; + ASSERT_EQ(values.GetArray()[0].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[0].GetString(), "Unspecified"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[0].GetString(), "Gas Giant"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[0].GetString(), "Waterworld"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[0].GetString(), "Agriworld"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[0].GetString(), "Ordnance"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[1].GetInteger(), 0); + ASSERT_EQ(values.GetArray()[1].GetObjects()[1].GetInteger(), 1); + ASSERT_EQ(values.GetArray()[2].GetObjects()[1].GetInteger(), 2); + ASSERT_EQ(values.GetArray()[3].GetObjects()[1].GetInteger(), 3); + ASSERT_EQ(values.GetArray()[4].GetObjects()[1].GetInteger(), 4); + + ASSERT_EQ(json.GetObjects()[2].GetName(), "id"); + ASSERT_EQ(json.GetObjects()[2].GetString(), "galaxy"); + } + + // Check property table. + constexpr int kRows = 16; + ASSERT_EQ(structural_metadata.NumPropertyTables(), 1); + const PropertyTable &table = structural_metadata.GetPropertyTable(0); + ASSERT_EQ(table.GetName(), "Galaxy far far away."); + ASSERT_EQ(table.GetClass(), "planet"); + ASSERT_EQ(table.GetCount(), kRows); + ASSERT_EQ(table.NumProperties(), 3); + + // Check property that describes RGB color components of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(0); + ASSERT_EQ(property.GetName(), "color"); + + ASSERT_EQ(property.GetData().data.size(), kRows * 3); // RGB components. + ASSERT_EQ(property.GetData().target, 34962); // ARRAY_BUFFER. + + ASSERT_EQ(property.GetData().data[0], 94); // Tatooine [94, 94, 194]. + ASSERT_EQ(property.GetData().data[1], 94); + ASSERT_EQ(property.GetData().data[2], 194); + ASSERT_EQ(property.GetData().data[18], 190); // Corellia [190, 92, 108]. + ASSERT_EQ(property.GetData().data[19], 92); + ASSERT_EQ(property.GetData().data[20], 108); + ASSERT_EQ(property.GetData().data[45], 0); // UNLABELED [0, 0, 0]. + ASSERT_EQ(property.GetData().data[46], 0); + ASSERT_EQ(property.GetData().data[47], 0); + + ASSERT_TRUE(property.GetArrayOffsets().type.empty()); + ASSERT_TRUE(property.GetArrayOffsets().data.data.empty()); + ASSERT_EQ(property.GetArrayOffsets().data.target, 0); + ASSERT_TRUE(property.GetStringOffsets().type.empty()); + ASSERT_TRUE(property.GetStringOffsets().data.data.empty()); + ASSERT_EQ(property.GetStringOffsets().data.target, 0); + } + + // Check property that describes names of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(1); + ASSERT_EQ(property.GetName(), "name"); + const std::vector &data = property.GetData().data; + const std::vector &offsets = property.GetStringOffsets().data.data; + + ASSERT_EQ(data.size(), 296); // Concatenated label strings. + ASSERT_EQ(property.GetData().target, 34963); // ELEMENT_ARRAY_BUFFER. + + ASSERT_EQ(property.GetStringOffsets().type, "UINT32"); + ASSERT_EQ(offsets.size(), 4 * (kRows + 1)); + ASSERT_EQ(property.GetStringOffsets().data.target, 34963); + + ASSERT_EQ(offsets[0], 0); // Tatooine 0. + ASSERT_EQ(offsets[1], 0); + ASSERT_EQ(offsets[2], 0); + ASSERT_EQ(offsets[3], 0); + ASSERT_EQ(offsets[60], 32); // UNLABELED 287. + ASSERT_EQ(offsets[61], 1); + ASSERT_EQ(offsets[62], 0); + ASSERT_EQ(offsets[63], 0); + ASSERT_EQ(offsets[64], 41); // Beyond UNLABELED 296. + ASSERT_EQ(offsets[65], 1); + ASSERT_EQ(offsets[66], 0); + ASSERT_EQ(offsets[67], 0); + + struct Name { + static std::string Extract(const std::vector &data, + const std::vector &offsets, int row) { + const int b = offsets[4 * (row + 0)] + 255 * offsets[4 * (row + 0) + 1]; + const int e = offsets[4 * (row + 1)] + 255 * offsets[4 * (row + 1) + 1]; + return std::string(data.begin() + b, data.begin() + e); + } + }; + + // Check that the names can be extracted from the data. + ASSERT_EQ(Name::Extract(data, offsets, 0), "named_class:Tatooine"); + ASSERT_EQ(Name::Extract(data, offsets, 6), "named_class:Corellia"); + ASSERT_EQ(Name::Extract(data, offsets, 15), "UNLABELED"); + + ASSERT_TRUE(property.GetArrayOffsets().type.empty()); + ASSERT_TRUE(property.GetArrayOffsets().data.data.empty()); + ASSERT_EQ(property.GetArrayOffsets().data.target, 0); + } + + // Check property that describes number sequence of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(2); + ASSERT_EQ(property.GetName(), "sequence"); + const std::vector &data = property.GetData().data; + const std::vector &offsets = property.GetArrayOffsets().data.data; + + ASSERT_EQ(data.size(), 41 * 4); // Concatenated float arrays. + ASSERT_EQ(property.GetData().target, 34963); // ELEMENT_ARRAY_BUFFER. + + ASSERT_EQ(property.GetArrayOffsets().type, "UINT8"); + ASSERT_EQ(offsets.size(), 20); // kRows + 1 + padding. + ASSERT_EQ(property.GetArrayOffsets().data.target, 34963); + + ASSERT_EQ(offsets[0], 0 * 4); // Tatooine + ASSERT_EQ(offsets[1], 6 * 4); // Corusant + ASSERT_EQ(offsets[6], 16 * 4); // Corellia + ASSERT_EQ(offsets[14], 36 * 4); // Geonosis + ASSERT_EQ(offsets[15], 41 * 4); // UNLABELED (empty array). + ASSERT_EQ(offsets[16], 41 * 4); // Beyond UNLABELED (empty array). + + struct Sequence { + static std::vector Extract(const std::vector &data, + const std::vector &offsets, + int row) { + const int n = (offsets[row + 1] - offsets[row]) / 4; + std::vector result; + result.reserve(n); + for (int i = 0; i < n; ++i) { + const void *const pointer = &data[offsets[row] + 4 * i]; + result.push_back(*static_cast(pointer)); + } + return result; + } + }; + + // Check that the number sequence arrays can be extracted from the data. + ASSERT_EQ( + Sequence::Extract(data, offsets, 0), + (std::vector{0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f})); // Tatooine + ASSERT_EQ(Sequence::Extract(data, offsets, 1), + (std::vector{6.5f, 7.5f})); // Corusant + ASSERT_EQ( + Sequence::Extract(data, offsets, 14), + (std::vector{36.5f, 37.5f, 38.5f, 39.5f, 40.5f})); // Geonosis + ASSERT_TRUE(Sequence::Extract(data, offsets, 15) + .empty()); // UNLABELED (empty array). + + ASSERT_TRUE(property.GetStringOffsets().type.empty()); + ASSERT_TRUE(property.GetStringOffsets().data.data.empty()); + ASSERT_EQ(property.GetStringOffsets().data.target, 0); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.h b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.h new file mode 100644 index 000000000..91aec9b08 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_test_helper.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ +#define DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" + +namespace draco { + +// Helper class for testing Draco glTF encoder and decoder. +class GltfTestHelper { + public: + // Adds various mesh feature ID sets (via attributes and via textures) and + // structural metadata property table and property table schema to the box + // |scene| loaded from the test file testdata/Box/glTF/Box.gltf. + static void AddBoxMetaMeshFeatures(Scene *scene); + static void AddBoxMetaStructuralMetadata(Scene *scene); + + // Checks the box |geometry| (draco::Mesh or draco::Scene) with mesh features + // loaded from one of these test files, with or without Draco compression: + // 1. testdata/BoxMeta/glTF/BoxMeta.gltf + // 2. testdata/BoxMetaDraco/glTF/BoxMetaDraco.gltf + template + static void CheckBoxMetaMeshFeatures(const GeometryT &geometry, + bool has_draco_compression); + + // Checks the box |geometry| (draco::Mesh or draco::Scene) with structural + // metadata that includes property table and property table schema loaded from + // test file testdata/BoxMeta/glTF/BoxMeta.gltf. + template + static void CheckBoxMetaStructuralMetadata(const GeometryT &geometry) { + CheckBoxMetaStructuralMetadata(geometry.GetStructuralMetadata()); + } + + private: + static void CheckBoxMetaMeshFeatures(const Mesh &mesh, + const TextureLibrary &texture_lib, + bool has_draco_compression); + static void CheckBoxMetaStructuralMetadata( + const StructuralMetadata &structural_metadata); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.cc new file mode 100644 index 000000000..bf5c048ef --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.cc @@ -0,0 +1,154 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_utils.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +namespace draco { + +std::ostream &operator<<(std::ostream &os, const GltfValue &value) { + if (value.type_ == GltfValue::INT) { + os << value.value_int_; + } else { + os << value.value_double_; + } + return os; +} + +Indent::Indent() : indent_space_count_(2) {} + +void Indent::Increase() { indent_ += std::string(indent_space_count_, ' '); } + +void Indent::Decrease() { indent_.erase(0, indent_space_count_); } + +std::ostream &operator<<(std::ostream &os, const Indent &indent) { + return os << indent.indent_; +} + +std::ostream &operator<<(std::ostream &os, + const JsonWriter::IndentWrapper &indent) { + if (indent.writer.mode_ == JsonWriter::READABLE) { + os << indent.writer.indent_writer_; + } + return os; +} + +std::ostream &operator<<(std::ostream &os, + const JsonWriter::Separator &separator) { + if (separator.writer.mode_ == JsonWriter::READABLE) { + os << " "; + } + return os; +} + +void JsonWriter::Reset() { + last_type_ = START; + o_.clear(); + o_.str(""); +} + +void JsonWriter::BeginObject() { BeginObject(""); } + +void JsonWriter::BeginObject(const std::string &name) { + FinishPreviousLine(BEGIN); + o_ << indent_; + if (!name.empty()) { + o_ << "\"" << name << "\":" << separator_; + } + o_ << "{"; + indent_writer_.Increase(); +} + +void JsonWriter::EndObject() { + FinishPreviousLine(END); + indent_writer_.Decrease(); + o_ << indent_ << "}"; +} + +void JsonWriter::BeginArray(const std::string &name) { + FinishPreviousLine(BEGIN); + o_ << indent_ << "\"" << name << "\":" << separator_ << "["; + indent_writer_.Increase(); +} + +void JsonWriter::EndArray() { + FinishPreviousLine(END); + indent_writer_.Decrease(); + o_ << indent_ << "]"; +} + +void JsonWriter::FinishPreviousLine(OutputType curr_type) { + if (last_type_ != START) { + if ((last_type_ == VALUE && curr_type == VALUE) || + (last_type_ == VALUE && curr_type == BEGIN) || + (last_type_ == END && curr_type == BEGIN) || + (last_type_ == END && curr_type == VALUE)) { + o_ << ","; + } + if (mode_ == READABLE) { + o_ << std::endl; + } + } + last_type_ = curr_type; +} + +std::string JsonWriter::MoveData() { + const std::string str = o_.str(); + o_.str(""); + return str; +} + +std::string JsonWriter::EscapeCharacter(const std::string &str, + const char character) { + size_t start = 0; + if ((start = str.find(character, start)) != std::string::npos) { + std::string s = str; + std::string escaped_character = "\\"; + escaped_character += character; + do { + s.replace(start, 1, escaped_character); + start += escaped_character.length(); + } while ((start = s.find(character, start)) != std::string::npos); + return s; + } + return str; +} + +std::string JsonWriter::EscapeJsonSpecialCharacters(const std::string &str) { + std::string s = str; + const char backspace = '\b'; + const char form_feed = '\f'; + const char newline = '\n'; + const char carriage_return = '\r'; + const char tab = '\t'; + const char double_quote = '\"'; + const char backslash = '\\'; + + // Backslash must come first. + s = EscapeCharacter(s, backslash); + s = EscapeCharacter(s, backspace); + s = EscapeCharacter(s, form_feed); + s = EscapeCharacter(s, newline); + s = EscapeCharacter(s, carriage_return); + s = EscapeCharacter(s, tab); + s = EscapeCharacter(s, double_quote); + return s; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.h new file mode 100644 index 000000000..2cf12fdc7 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils.h @@ -0,0 +1,186 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_UTILS_H_ +#define DRACO_IO_GLTF_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +namespace draco { + +// Class used to store integer or float values supported by glTF. +class GltfValue { + public: + enum ValueType { INT, DOUBLE }; + + explicit GltfValue(int8_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint8_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(int16_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint16_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint32_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(float value) + : type_(DOUBLE), value_int_(-1), value_double_(value) {} + + friend std::ostream &operator<<(std::ostream &os, const GltfValue &value); + + private: + ValueType type_; + int64_t value_int_; + double value_double_; +}; + +// Utility class used to help with indentation of glTF file. +class Indent { + public: + Indent(); + + void Increase(); + void Decrease(); + + friend std::ostream &operator<<(std::ostream &os, const Indent &indent); + + private: + // Variables used for spacing of the glTF file. + std::string indent_; + const int indent_space_count_; +}; + +// Class used to keep track of the json state. +class JsonWriter { + public: + enum OutputType { START, BEGIN, END, VALUE }; + enum Mode { READABLE, COMPACT }; + + JsonWriter() + : last_type_(START), mode_(READABLE), indent_(*this), separator_(*this) {} + void SetMode(Mode mode) { mode_ = mode; } + + // Clear the stringstream and set last type to START. + void Reset(); + + // Every call to BeginObject should have a matching call to EndObject. + void BeginObject(); + void BeginObject(const std::string &name); + void EndObject(); + + // Every call to BeginArray should have a matching call to EndArray. + void BeginArray(const std::string &name); + void EndArray(); + + template + void OutputValue(const T &value) { + FinishPreviousLine(VALUE); + o_ << indent_ << std::setprecision(17) << value; + } + + void OutputValue(const bool &value) { + FinishPreviousLine(VALUE); + o_ << indent_ << ToString(value); + } + + void OutputValue(const std::string &name) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\""; + } + + void OutputValue(const std::string &name, const std::string &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + const std::string escaped_value = EscapeJsonSpecialCharacters(value); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << "\"" + << escaped_value << "\""; + } + + void OutputValue(const std::string &name, const char *value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + const std::string escaped_value = EscapeJsonSpecialCharacters(value); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << "\"" + << escaped_value << "\""; + } + + template + void OutputValue(const std::string &name, const T &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << value; + } + + void OutputValue(const std::string &name, const bool &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ + << ToString(value); + } + + // Return the current output and then clear the stringstream. + std::string MoveData(); + + private: + // Check if a comma needs to be added to the output and then add a new line. + void FinishPreviousLine(OutputType curr_type); + + // Returns a string escaping all instances of |character| in |str|. + std::string EscapeCharacter(const std::string &str, const char character); + + // Returns a string escaping all of the Json special characters in |str|. + // Carriage return is not handled. + std::string EscapeJsonSpecialCharacters(const std::string &str); + + // Returns string representation of a Boolean |value|. + static std::string ToString(bool value) { return value ? "true" : "false"; } + + // Helper struct used for conditional indent writing to the output stream. + struct IndentWrapper { + explicit IndentWrapper(const JsonWriter &writer) : writer(writer) {} + const JsonWriter &writer; + }; + friend std::ostream &operator<<(std::ostream &os, + const IndentWrapper &indent); + + // Helper struct used for conditional separator writing to the output stream. + struct Separator { + explicit Separator(const JsonWriter &writer) : writer(writer) {} + const JsonWriter &writer; + }; + friend std::ostream &operator<<(std::ostream &os, const Separator &separator); + + std::stringstream o_; + Indent indent_writer_; + OutputType last_type_; + Mode mode_; + IndentWrapper indent_; + Separator separator_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils_test.cc new file mode 100644 index 000000000..01a2d144c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/gltf_utils_test.cc @@ -0,0 +1,366 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { + +class GltfUtilsTest : public ::testing::Test { + protected: + void CompareGolden(JsonWriter *json_writer, const std::string &golden_str) { + const std::string json = json_writer->MoveData(); + ASSERT_EQ(golden_str, json); + } +}; + +TEST_F(GltfUtilsTest, TestNoData) { + const std::string golden = ""; + JsonWriter json_writer; + CompareGolden(&json_writer, golden); +} + +TEST_F(GltfUtilsTest, TestValues) { + JsonWriter json_writer; + json_writer.OutputValue(0); + CompareGolden(&json_writer, "0"); + + json_writer.Reset(); + json_writer.OutputValue(1); + CompareGolden(&json_writer, "1"); + + json_writer.Reset(); + json_writer.OutputValue(-1); + CompareGolden(&json_writer, "-1"); + + json_writer.Reset(); + json_writer.OutputValue(0.0); + CompareGolden(&json_writer, "0"); + + json_writer.Reset(); + json_writer.OutputValue(1.0); + CompareGolden(&json_writer, "1"); + + json_writer.Reset(); + json_writer.OutputValue(0.25); + CompareGolden(&json_writer, "0.25"); + + json_writer.Reset(); + json_writer.OutputValue(-0.25); + CompareGolden(&json_writer, "-0.25"); + + json_writer.Reset(); + json_writer.OutputValue(false); + CompareGolden(&json_writer, "false"); + + json_writer.Reset(); + json_writer.OutputValue(true); + CompareGolden(&json_writer, "true"); + + json_writer.Reset(); + json_writer.OutputValue("test int", -1); + CompareGolden(&json_writer, "\"test int\": -1"); + + json_writer.Reset(); + json_writer.OutputValue("test float", -10.25); + CompareGolden(&json_writer, "\"test float\": -10.25"); + + json_writer.Reset(); + json_writer.OutputValue("test char*", "I am the string!"); + CompareGolden(&json_writer, "\"test char*\": \"I am the string!\""); + + json_writer.Reset(); + const std::string value = "I am the string!"; + json_writer.OutputValue("test string", value); + CompareGolden(&json_writer, "\"test string\": \"I am the string!\""); + + json_writer.Reset(); + json_writer.OutputValue("test bool", false); + CompareGolden(&json_writer, "\"test bool\": false"); + + json_writer.Reset(); + json_writer.OutputValue("test bool", true); + CompareGolden(&json_writer, "\"test bool\": true"); +} + +TEST_F(GltfUtilsTest, TestSpecialCharacters) { + JsonWriter json_writer; + const std::string test_double_quote = "I am double quote\""; + json_writer.OutputValue("test double quote", test_double_quote); + CompareGolden(&json_writer, + "\"test double quote\": \"I am double quote\\\"\""); + + json_writer.Reset(); + const std::string test_backspace = "I am backspace\b"; + json_writer.OutputValue("test backspace", test_backspace); + CompareGolden(&json_writer, "\"test backspace\": \"I am backspace\\\b\""); + + json_writer.Reset(); + const std::string test_form_feed = "I am form feed\f"; + json_writer.OutputValue("test form feed", test_form_feed); + CompareGolden(&json_writer, "\"test form feed\": \"I am form feed\\\f\""); + + json_writer.Reset(); + const std::string test_newline = "I am newline\n"; + json_writer.OutputValue("test newline", test_newline); + CompareGolden(&json_writer, "\"test newline\": \"I am newline\\\n\""); + + json_writer.Reset(); + const std::string test_tab = "I am tab\t"; + json_writer.OutputValue("test tab", test_tab); + CompareGolden(&json_writer, "\"test tab\": \"I am tab\\\t\""); + + json_writer.Reset(); + const std::string test_backslash = "I am backslash\\"; + json_writer.OutputValue("test backslash", test_backslash); + CompareGolden(&json_writer, "\"test backslash\": \"I am backslash\\\\\""); + + json_writer.Reset(); + const std::string test_multiple_special_characters = "\"break\"and\\more\"\\"; + json_writer.OutputValue("test multiple_special_characters", + test_multiple_special_characters); + CompareGolden(&json_writer, + "\"test multiple_special_characters\": " + "\"\\\"break\\\"and\\\\more\\\"\\\\\""); +} + +TEST_F(GltfUtilsTest, TestObjects) { + JsonWriter json_writer; + json_writer.BeginObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "{\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n 0\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n 0,\n 1,\n 2,\n 3\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.EndObject(); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\": {\n},\n\"object2\": {\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\": {\n \"object2\": {\n }\n}"); +} + +TEST_F(GltfUtilsTest, TestArrays) { + JsonWriter json_writer; + json_writer.BeginArray("array"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n 0\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n 0,\n 1,\n 2,\n 3\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.EndArray(); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\": [\n],\n\"array2\": [\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\": [\n \"array2\": [\n ]\n]"); +} + +TEST_F(GltfUtilsTest, TestGltfValues) { + JsonWriter json_writer; + const int8_t int8_value_min = std::numeric_limits::min(); + const int8_t int8_value_max = std::numeric_limits::max(); + const GltfValue int8_value_low(int8_value_min); + const GltfValue int8_value_high(int8_value_max); + json_writer.OutputValue(int8_value_low); + json_writer.OutputValue(int8_value_high); + CompareGolden(&json_writer, "-128,\n127"); + + json_writer.Reset(); + const uint8_t uint8_value_min = std::numeric_limits::min(); + const uint8_t uint8_value_max = std::numeric_limits::max(); + const GltfValue uint8_value_low(uint8_value_min); + const GltfValue uint8_value_high(uint8_value_max); + json_writer.OutputValue(uint8_value_low); + json_writer.OutputValue(uint8_value_high); + CompareGolden(&json_writer, "0,\n255"); + + json_writer.Reset(); + const int16_t int16_value_min = std::numeric_limits::min(); + const int16_t int16_value_max = std::numeric_limits::max(); + const GltfValue int16_value_low(int16_value_min); + const GltfValue int16_value_high(int16_value_max); + json_writer.OutputValue(int16_value_low); + json_writer.OutputValue(int16_value_high); + CompareGolden(&json_writer, "-32768,\n32767"); + + json_writer.Reset(); + const uint16_t uint16_value_min = std::numeric_limits::min(); + const uint16_t uint16_value_max = std::numeric_limits::max(); + const GltfValue uint16_value_low(uint16_value_min); + const GltfValue uint16_value_high(uint16_value_max); + json_writer.OutputValue(uint16_value_low); + json_writer.OutputValue(uint16_value_high); + CompareGolden(&json_writer, "0,\n65535"); + + json_writer.Reset(); + const uint32_t uint32_value_min = std::numeric_limits::min(); + const uint32_t uint32_value_max = std::numeric_limits::max(); + const GltfValue uint32_value_low(uint32_value_min); + const GltfValue uint32_value_high(uint32_value_max); + json_writer.OutputValue(uint32_value_low); + json_writer.OutputValue(uint32_value_high); + CompareGolden(&json_writer, "0,\n4294967295"); + + json_writer.Reset(); + const float float_value_min = std::numeric_limits::min(); + const float float_value_max = std::numeric_limits::max(); + const GltfValue float_value_low(float_value_min); + const GltfValue float_value_high(float_value_max); + json_writer.OutputValue(float_value_low); + json_writer.OutputValue(float_value_high); + CompareGolden(&json_writer, + "1.1754943508222875e-38,\n3.4028234663852886e+38"); + + json_writer.Reset(); + const GltfValue float_value_0(0.1f); + const GltfValue float_value_1(1.f); + json_writer.OutputValue(float_value_0); + json_writer.OutputValue(float_value_1); + CompareGolden(&json_writer, "0.10000000149011612,\n1"); +} + +TEST_F(GltfUtilsTest, TestObjectsCompact) { + JsonWriter json_writer; + json_writer.SetMode(JsonWriter::COMPACT); + json_writer.BeginObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "{}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{0}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{0,1,2,3}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.EndObject(); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\":{},\"object2\":{}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\":{\"object2\":{}}"); +} + +TEST_F(GltfUtilsTest, TestArraysCompact) { + JsonWriter json_writer; + json_writer.SetMode(JsonWriter::COMPACT); + json_writer.BeginArray("array"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[0]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[0,1,2,3]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.EndArray(); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\":[],\"array2\":[]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\":[\"array2\":[]]"); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/image_compression_options.h b/Engine/lib/assimp/contrib/draco/src/draco/io/image_compression_options.h new file mode 100644 index 000000000..722bdbd64 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/image_compression_options.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ +#define DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +namespace draco { + +// Enum defining image compression formats. +enum class ImageFormat { NONE, PNG, JPEG, BASIS, WEBP }; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/mesh_io.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/mesh_io.cc index e0dc69c6f..4975d9236 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/mesh_io.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/mesh_io.cc @@ -18,8 +18,18 @@ #include #include "draco/io/file_utils.h" +#include "draco/io/file_writer_interface.h" #include "draco/io/obj_decoder.h" #include "draco/io/ply_decoder.h" +#include "draco/io/stl_decoder.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/compression/encode.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/obj_encoder.h" +#include "draco/io/ply_encoder.h" +#endif namespace draco { @@ -46,27 +56,40 @@ StatusOr> ReadMeshFromFile( std::unique_ptr mesh(new Mesh()); // Analyze file extension. const std::string extension = LowercaseFileExtension(file_name); - if (extension != "gltf" && mesh_files) { - // The GLTF decoder will fill |mesh_files|, but for other file types we set - // the root file here to avoid duplicating code. + if (extension != "gltf" && extension != "obj" && mesh_files) { + // The GLTF/OBJ decoder will fill |mesh_files|, but for other file types we + // set the root file here to avoid duplicating code. mesh_files->push_back(file_name); } if (extension == "obj") { // Wavefront OBJ file format. ObjDecoder obj_decoder; obj_decoder.set_use_metadata(options.GetBool("use_metadata", false)); - const Status obj_status = obj_decoder.DecodeFromFile(file_name, mesh.get()); + obj_decoder.set_preserve_polygons(options.GetBool("preserve_polygons")); + const Status obj_status = + obj_decoder.DecodeFromFile(file_name, mesh.get(), mesh_files); if (!obj_status.ok()) { return obj_status; } return std::move(mesh); } if (extension == "ply") { - // Wavefront PLY file format. + // Stanford PLY file format. PlyDecoder ply_decoder; DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, mesh.get())); return std::move(mesh); } + if (extension == "stl") { + // STL file format. + StlDecoder stl_decoder; + return stl_decoder.DecodeFromFile(file_name); + } +#ifdef DRACO_TRANSCODER_SUPPORTED + if (extension == "gltf" || extension == "glb") { + GltfDecoder gltf_decoder; + return gltf_decoder.DecodeFromFile(file_name, mesh_files); + } +#endif // Otherwise not an obj file. Assume the file was encoded with one of the // draco encoding methods. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.cc index 9b4eab626..c233c2b56 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.cc @@ -14,8 +14,10 @@ // #include "draco/io/obj_decoder.h" +#include #include #include +#include #include "draco/io/file_utils.h" #include "draco/io/parser_utils.h" @@ -36,15 +38,25 @@ ObjDecoder::ObjDecoder() norm_att_id_(-1), material_att_id_(-1), sub_obj_att_id_(-1), + added_edge_att_id_(-1), deduplicate_input_values_(true), last_material_id_(0), use_metadata_(false), + preserve_polygons_(false), + has_polygons_(false), + mesh_files_(nullptr), out_mesh_(nullptr), out_point_cloud_(nullptr) {} Status ObjDecoder::DecodeFromFile(const std::string &file_name, Mesh *out_mesh) { + return DecodeFromFile(file_name, out_mesh, nullptr); +} + +Status ObjDecoder::DecodeFromFile(const std::string &file_name, Mesh *out_mesh, + std::vector *mesh_files) { out_mesh_ = out_mesh; + mesh_files_ = mesh_files; return DecodeFromFile(file_name, static_cast(out_mesh)); } @@ -90,6 +102,10 @@ Status ObjDecoder::DecodeInternal() { return status; } + if (mesh_files_ && !input_file_name_.empty()) { + mesh_files_->push_back(input_file_name_); + } + bool use_identity_mapping = false; if (num_obj_faces_ == 0) { // Mesh has no faces. In this case we try to read the geometry as a point @@ -146,6 +162,24 @@ Status ObjDecoder::DecodeInternal() { norm_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping, num_normals_); } + if (preserve_polygons_ && has_polygons_) { + // Create attribute for polygon reconstruction. + GeometryAttribute va; + va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0); + PointCloud *const pc = out_point_cloud_; + added_edge_att_id_ = pc->AddAttribute(va, false, 2); + + // Set attribute values to zero and one representing old edge and new edge. + for (const uint8_t i : {0, 1}) { + const AttributeValueIndex avi(i); + pc->attribute(added_edge_att_id_)->SetAttributeValue(avi, &i); + } + + // Add attribute metadata with name. + std::unique_ptr metadata(new draco::AttributeMetadata()); + metadata->AddEntryString("name", "added_edges"); + pc->AddAttributeMetadata(added_edge_att_id_, std::move(metadata)); + } if (num_materials_ > 0 && num_obj_faces_ > 0) { GeometryAttribute va; const auto geometry_attribute_type = GeometryAttribute::GENERIC; @@ -381,6 +415,7 @@ bool ObjDecoder::ParseTexCoord(Status *status) { } bool ObjDecoder::ParseFace(Status *status) { + constexpr int kMaxCorners = 8; char c; if (!buffer()->Peek(&c)) { return false; @@ -391,37 +426,35 @@ bool ObjDecoder::ParseFace(Status *status) { // Face definition found! buffer()->Advance(1); if (!counting_mode_) { - std::array indices[4]; - // Parse face indices (we try to look for up to four to support quads). + std::array indices[kMaxCorners]; + // Parse face indices. int num_valid_indices = 0; - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < kMaxCorners; ++i) { if (!ParseVertexIndices(&indices[i])) { - if (i == 3) { - break; // It's OK if there is no fourth vertex index. + if (i >= 3) { + break; // It's OK if there is no fourth or higher vertex index. } *status = Status(Status::DRACO_ERROR, "Failed to parse vertex indices"); return true; } ++num_valid_indices; } - // Process the first face. - for (int i = 0; i < 3; ++i) { - const PointIndex vert_id(3 * num_obj_faces_ + i); - MapPointToVertexIndices(vert_id, indices[i]); - } - ++num_obj_faces_; - if (num_valid_indices == 4) { - // Add an additional triangle for the quad. - // - // 3----2 - // | / | - // | / | - // 0----1 - // - const PointIndex vert_id(3 * num_obj_faces_); - MapPointToVertexIndices(vert_id, indices[0]); - MapPointToVertexIndices(vert_id + 1, indices[2]); - MapPointToVertexIndices(vert_id + 2, indices[3]); + // Split quads and other n-gons into n - 2 triangles. + const int nt = num_valid_indices - 2; + // Iterate over triangles. + for (int t = 0; t < nt; t++) { + // Iterate over corners. + for (int c = 0; c < 3; c++) { + const PointIndex vert_id(3 * num_obj_faces_ + c); + const int triangulated_index = Triangulate(t, c); + MapPointToVertexIndices(vert_id, indices[triangulated_index]); + // Save info about new edges that will allow us to reconstruct polygons. + if (added_edge_att_id_ >= 0) { + const AttributeValueIndex avi(IsNewEdge(nt, t, c)); + out_point_cloud_->attribute(added_edge_att_id_) + ->SetPointMapEntry(vert_id, avi); + } + } ++num_obj_faces_; } } else { @@ -443,12 +476,14 @@ bool ObjDecoder::ParseFace(Status *status) { } } } - if (num_indices < 3 || num_indices > 4) { - *status = - Status(Status::DRACO_ERROR, "Invalid number of indices on a face"); + if (num_indices > 3) { + has_polygons_ = true; + } + if (num_indices < 3 || num_indices > kMaxCorners) { + *status = ErrorStatus("Invalid number of indices on a face"); return false; } - // Either one or two new triangles. + // Either one or more new triangles. num_obj_faces_ += num_indices - 2; } parser::SkipLine(buffer()); @@ -478,6 +513,9 @@ bool ObjDecoder::ParseMaterialLib(Status *status) { parser::SkipLine(&line_buffer); if (!material_file_name_.empty()) { + if (mesh_files_) { + mesh_files_->push_back(material_file_name_); + } if (!ParseMaterialFile(material_file_name_, status)) { // Silently ignore problems with material files for now. return true; @@ -705,4 +743,44 @@ bool ObjDecoder::ParseMaterialFileDefinition(Status * /* status */) { return true; } +// Methods Triangulate() and IsNewEdge() are used for polygon triangulation and +// representation as an attribute for reconstruction in the decoder. +// +// Polygon reconstruction attribute is associated with every triangle corner and +// has values zero or one. Zero indicates that an edge opposite to the corner is +// present in the original mesh (dashed lines), and one indicates that the +// opposite edge has been added during polygon triangulation (dotted lines). +// +// Polygon triangulation is illustrated below. Pentagon ABCDE is split into +// three triangles ABC, ACD, ADE. It is sufficient to set polygon reconstruction +// attribute at corners ABC and ACD. The attribute at the second corner of all +// triangles except for the last is set to one. +// +// C D +// * --------- * +// /. 1 0 .| +// / . . | +// / . . | +// / 0 . . 0 | +// / . . | +// B * 1 . . | +// \ . . | +// \ 0 . 0 . | +// \ . . | +// \ . . | +// \.. 0 0 | +// *-----------* +// A E +// +inline int ObjDecoder::Triangulate(int tri_index, int tri_corner) { + return tri_corner == 0 ? 0 : tri_index + tri_corner; +} + +inline bool ObjDecoder::IsNewEdge(int tri_count, int tri_index, + int tri_corner) { + // All but the last triangle of the triangulated polygon have an added edge + // opposite of corner 1. + return tri_index != tri_count - 1 && tri_corner == 1; +} + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.h index baeab5b0c..18dc9aadd 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder.h @@ -34,8 +34,12 @@ class ObjDecoder { ObjDecoder(); // Decodes an obj file stored in the input file. - // Returns nullptr if the decoding failed. + // Optional argument |mesh_files| will be populated with all paths to files + // relevant to the loaded mesh. Status DecodeFromFile(const std::string &file_name, Mesh *out_mesh); + Status DecodeFromFile(const std::string &file_name, Mesh *out_mesh, + std::vector *mesh_files); + Status DecodeFromFile(const std::string &file_name, PointCloud *out_point_cloud); @@ -50,6 +54,8 @@ class ObjDecoder { // Flag for whether using metadata to record other information in the obj // file, e.g. material names, object names. void set_use_metadata(bool flag) { use_metadata_ = flag; } + // Enables preservation of polygons. + void set_preserve_polygons(bool flag) { preserve_polygons_ = flag; } protected: Status DecodeInternal(); @@ -88,6 +94,11 @@ class ObjDecoder { bool ParseMaterialFile(const std::string &file_name, Status *status); bool ParseMaterialFileDefinition(Status *status); + // Methods related to polygon triangulation and preservation. + static int Triangulate(int tri_index, int tri_corner); + static bool IsNewEdge(int tri_count, int tri_index, int tri_corner); + + private: // If set to true, the parser will count the number of various definitions // but it will not parse the actual data or add any new entries to the mesh. bool counting_mode_; @@ -102,7 +113,8 @@ class ObjDecoder { int tex_att_id_; int norm_att_id_; int material_att_id_; - int sub_obj_att_id_; // Attribute id for storing sub-objects. + int sub_obj_att_id_; // Attribute id for storing sub-objects. + int added_edge_att_id_; // Attribute id for polygon reconstruction. bool deduplicate_input_values_; @@ -116,6 +128,12 @@ class ObjDecoder { bool use_metadata_; + // Polygon preservation flags. + bool preserve_polygons_; + bool has_polygons_; + + std::vector *mesh_files_; + DecoderBuffer buffer_; // Data structure that stores the decoded data. |out_point_cloud_| must be diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder_test.cc index b19fe6e2c..a46a15a8b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_decoder_test.cc @@ -54,6 +54,20 @@ class ObjDecoderTest : public ::testing::Test { return geometry; } + template + std::unique_ptr DecodeObjWithPolygons( + const std::string &file_name, bool regularize_quads, + bool store_added_edges_per_vertex) const { + const std::string path = GetTestFileFullPath(file_name); + ObjDecoder decoder; + decoder.set_preserve_polygons(true); + std::unique_ptr geometry(new Geometry()); + if (!decoder.DecodeFromFile(path, geometry.get()).ok()) { + return nullptr; + } + return geometry; + } + void test_decoding(const std::string &file_name) { const std::unique_ptr mesh(DecodeObj(file_name)); ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; @@ -113,7 +127,7 @@ TEST_F(ObjDecoderTest, SubObjectsWithMetadata) { ASSERT_EQ(sub_obj_id, 2); } -TEST_F(ObjDecoderTest, QuadOBJ) { +TEST_F(ObjDecoderTest, QuadTriangulateOBJ) { // Tests loading an Obj with quad faces. const std::string file_name = "cube_quads.obj"; const std::unique_ptr mesh(DecodeObj(file_name)); @@ -124,11 +138,114 @@ TEST_F(ObjDecoderTest, QuadOBJ) { ASSERT_EQ(mesh->num_points(), 4 * 6); // Four points per quad face. } -TEST_F(ObjDecoderTest, ComplexPolyOBJ) { - // Tests that we fail to load an obj with complex polygon (expected failure). - const std::string file_name = "invalid/complex_poly.obj"; +TEST_F(ObjDecoderTest, QuadPreserveOBJ) { + // Tests loading an Obj with quad faces preserved as an attribute. + const std::string file_name = "cube_quads.obj"; + constexpr bool kRegularizeQuads = false; + constexpr bool kStoreAddedEdgesPerVertex = false; + const std::unique_ptr mesh(DecodeObjWithPolygons( + file_name, kRegularizeQuads, kStoreAddedEdgesPerVertex)); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + ASSERT_EQ(mesh->num_faces(), 12); + + ASSERT_EQ(mesh->num_attributes(), 4); + ASSERT_EQ(mesh->num_points(), 4 * 6); // Four points per quad face. + + // Expect a new generic attribute. + ASSERT_EQ(mesh->attribute(3)->attribute_type(), GeometryAttribute::GENERIC); + + // Expect the new attribute to have two values to describe old and new edge. + ASSERT_EQ(mesh->attribute(3)->size(), 2); + const auto new_edge_value = + mesh->attribute(3)->GetValue(AttributeValueIndex(0))[0]; + const auto old_edge_value = + mesh->attribute(3)->GetValue(AttributeValueIndex(1))[0]; + ASSERT_EQ(new_edge_value, 0); + ASSERT_EQ(old_edge_value, 1); + + // Expect one new edge on each of the six cube quads. + for (int i = 0; i < 6; i++) { + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 0)), 0); + // New edge. + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 1)), 1); + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 2)), 0); + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 3)), 0); + } + + // Expect metadata entry on the new attribute. + const AttributeMetadata *const metadata = + mesh->GetAttributeMetadataByAttributeId(3); + ASSERT_NE(metadata, nullptr); + ASSERT_TRUE(metadata->sub_metadatas().empty()); + ASSERT_EQ(metadata->entries().size(), 1); + std::string name; + metadata->GetEntryString("name", &name); + ASSERT_EQ(name, "added_edges"); +} + +TEST_F(ObjDecoderTest, OctagonTriangulatedOBJ) { + // Tests that we can load an obj with an octagon triangulated. + const std::string file_name = "octagon.obj"; const std::unique_ptr mesh(DecodeObj(file_name)); - ASSERT_EQ(mesh, nullptr); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + + ASSERT_EQ(mesh->num_attributes(), 1); + ASSERT_EQ(mesh->num_points(), 8); + ASSERT_EQ(mesh->attribute(0)->attribute_type(), GeometryAttribute::POSITION); + ASSERT_EQ(mesh->attribute(0)->size(), 8); +} + +TEST_F(ObjDecoderTest, OctagonPreservedOBJ) { + // Tests that we can load an obj with an octagon preserved as an attribute. + const std::string file_name = "octagon.obj"; + constexpr bool kRegularizeQuads = false; + constexpr bool kStoreAddedEdgesPerVertex = false; + const std::unique_ptr mesh(DecodeObjWithPolygons( + file_name, kRegularizeQuads, kStoreAddedEdgesPerVertex)); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + + ASSERT_EQ(mesh->num_attributes(), 2); + ASSERT_EQ(mesh->attribute(0)->attribute_type(), GeometryAttribute::POSITION); + ASSERT_EQ(mesh->attribute(0)->size(), 8); + + // Expect a new generic attribute. + ASSERT_EQ(mesh->attribute(1)->attribute_type(), GeometryAttribute::GENERIC); + + // There are four vertices with both old and new edges in their ring. + ASSERT_EQ(mesh->num_points(), 8 + 4); + + // Expect the new attribute to have two values to describe old and new edge. + ASSERT_EQ(mesh->attribute(1)->size(), 2); + const auto new_edge_value = + mesh->attribute(1)->GetValue(AttributeValueIndex(0))[0]; + const auto old_edge_value = + mesh->attribute(1)->GetValue(AttributeValueIndex(1))[0]; + ASSERT_EQ(new_edge_value, 0); + ASSERT_EQ(old_edge_value, 1); + + // Five new edges are introduced while triangulating as octagon. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(0)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(1)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(2)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(3)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(4)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(5)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(6)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(7)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(8)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(9)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(10)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(11)), 0); + + // Expect metadata entry on the new attribute. + const AttributeMetadata *const metadata = + mesh->GetAttributeMetadataByAttributeId(1); + ASSERT_NE(metadata, nullptr); + ASSERT_TRUE(metadata->sub_metadatas().empty()); + ASSERT_EQ(metadata->entries().size(), 1); + std::string name; + metadata->GetEntryString("name", &name); + ASSERT_EQ(name, "added_edges"); } TEST_F(ObjDecoderTest, EmptyNameOBJ) { @@ -167,7 +284,6 @@ TEST_F(ObjDecoderTest, WrongAttributeMapping) { TEST_F(ObjDecoderTest, TestObjDecodingAll) { // test if we can read all obj that are currently in test folder. test_decoding("bunny_norm.obj"); - // test_decoding("complex_poly.obj"); // not supported see test above test_decoding("cube_att.obj"); test_decoding("cube_att_partial.obj"); test_decoding("cube_att_sub_o.obj"); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.cc index 29c6ca8f0..1ddfd92bd 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.cc @@ -16,8 +16,10 @@ #include +#include "draco/attributes/geometry_attribute.h" #include "draco/io/file_writer_factory.h" #include "draco/io/file_writer_interface.h" +#include "draco/mesh/mesh_misc_functions.h" #include "draco/metadata/geometry_metadata.h" namespace draco { @@ -28,6 +30,7 @@ ObjEncoder::ObjEncoder() normal_att_(nullptr), material_att_(nullptr), sub_obj_att_(nullptr), + added_edges_att_(nullptr), out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr), @@ -78,11 +81,15 @@ bool ObjEncoder::EncodeInternal() { normal_att_ = nullptr; material_att_ = nullptr; sub_obj_att_ = nullptr; + added_edges_att_ = nullptr; current_sub_obj_id_ = -1; current_material_id_ = -1; if (!GetSubObjects()) { return false; } + if (in_mesh_ && !GetAddedEdges()) { + return false; + } if (!EncodeMaterialFileName()) { return false; } @@ -110,12 +117,38 @@ bool ObjEncoder::ExitAndCleanup(bool return_value) { normal_att_ = nullptr; material_att_ = nullptr; sub_obj_att_ = nullptr; + added_edges_att_ = nullptr; current_sub_obj_id_ = -1; current_material_id_ = -1; file_name_.clear(); return return_value; } +bool ObjEncoder::GetAddedEdges() { + const GeometryMetadata *mesh_metadata = in_mesh_->GetMetadata(); + if (!mesh_metadata) { + return true; + } + + // Try to get a per-corner attribute describing added edges. + { + const AttributeMetadata *att_metadata = + mesh_metadata->GetAttributeMetadataByStringEntry("name", "added_edges"); + if (att_metadata) { + const auto att = + in_mesh_->GetAttributeByUniqueId(att_metadata->att_unique_id()); + if (att->size() == 0 || att->num_components() != 1 || + att->data_type() != DataType::DT_UINT8) { + return false; + } + added_edges_att_ = att; + return true; + } + } + + return true; +} + bool ObjEncoder::GetSubObjects() { const GeometryMetadata *pc_metadata = in_point_cloud_->GetMetadata(); if (!pc_metadata) { @@ -137,7 +170,8 @@ bool ObjEncoder::GetSubObjects() { } sub_obj_att_ = in_point_cloud_->GetAttributeByUniqueId( sub_obj_metadata->att_unique_id()); - if (sub_obj_att_ == nullptr || sub_obj_att_->size() == 0) { + if (sub_obj_att_ == nullptr || sub_obj_att_->size() == 0 || + sub_obj_att_->num_components() != 1) { return false; } return true; @@ -236,17 +270,11 @@ bool ObjEncoder::EncodeNormals() { } bool ObjEncoder::EncodeFaces() { + if (added_edges_att_ != nullptr) { + return EncodePolygonalFaces(); + } for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) { - if (sub_obj_att_) { - if (!EncodeSubObject(i)) { - return false; - } - } - if (material_att_) { - if (!EncodeMaterial(i)) { - return false; - } - } + EncodeFaceAttributes(i); buffer()->Encode('f'); for (int j = 0; j < 3; ++j) { if (!EncodeFaceCorner(i, j)) { @@ -258,6 +286,56 @@ bool ObjEncoder::EncodeFaces() { return true; } +bool ObjEncoder::EncodePolygonalFaces() { + // TODO(vytyaz): This could be a much smaller set of visited face indices. + std::vector triangle_visited(in_mesh_->num_faces(), false); + PolygonEdges polygon_edges; + std::unique_ptr corner_table = + CreateCornerTableFromPositionAttribute(in_mesh_); + for (FaceIndex fi(0); fi < in_mesh_->num_faces(); ++fi) { + EncodeFaceAttributes(fi); + // Reconstruct polygon from the added edges attribute if available. + polygon_edges.clear(); + FindOriginalFaceEdges(fi, *corner_table, &triangle_visited, &polygon_edges); + + // Polygon edges could be empty if this triangle has been visited as part + // of a polygon discovery that started from an earler face. + if (polygon_edges.empty()) { + continue; + } + + // Traverse a polygon by following its edges. The starting point is not + // guaranteed to be the same as in the original polygon. It is + // deterministic, however, and defined by std::map behavior. + const AttributeValueIndex first_position_index = + polygon_edges.begin()->first; + AttributeValueIndex position_index = first_position_index; + buffer()->Encode('f'); + do { + // Get the next polygon point index by following polygon edge. + const PointIndex pi = polygon_edges[position_index]; + EncodeFaceCorner(pi); + position_index = pos_att_->mapped_index(pi).value(); + } while (position_index != first_position_index); + buffer()->Encode("\n", 1); + } + return true; +} + +bool ObjEncoder::EncodeFaceAttributes(FaceIndex face_id) { + if (sub_obj_att_) { + if (!EncodeSubObject(face_id)) { + return false; + } + } + if (material_att_) { + if (!EncodeMaterial(face_id)) { + return false; + } + } + return true; +} + bool ObjEncoder::EncodeMaterial(FaceIndex face_id) { int material_id = 0; // Pick the first corner, all corners of a face should have same id. @@ -304,8 +382,12 @@ bool ObjEncoder::EncodeSubObject(FaceIndex face_id) { } bool ObjEncoder::EncodeFaceCorner(FaceIndex face_id, int local_corner_id) { - buffer()->Encode(' '); const PointIndex vert_index = in_mesh_->face(face_id)[local_corner_id]; + return EncodeFaceCorner(vert_index); +} + +bool ObjEncoder::EncodeFaceCorner(PointIndex vert_index) { + buffer()->Encode(' '); // Note that in the OBJ format, all indices are encoded starting from index 1. // Encode position index. EncodeInt(pos_att_->mapped_index(vert_index).value() + 1); @@ -343,4 +425,67 @@ void ObjEncoder::EncodeInt(int32_t val) { buffer()->Encode(num_buffer_, strlen(num_buffer_)); } +bool ObjEncoder::IsNewEdge(const CornerTable &ct, CornerIndex ci) const { + const PointIndex pi = in_mesh_->CornerToPointId(ci); + if (added_edges_att_ != nullptr) { + uint8_t value; + added_edges_att_->GetMappedValue(pi, &value); + return value == 1; + } + return false; +} + +void ObjEncoder::FindOriginalFaceEdges(FaceIndex face_index, + const CornerTable &corner_table, + std::vector *triangle_visited, + PolygonEdges *polygon_edges) { + // Do not add any edges if this triangular face has already been visited. + if ((*triangle_visited)[face_index.value()]) { + return; + } + (*triangle_visited)[face_index.value()] = true; + const Mesh::Face &face = in_mesh_->face(face_index); + for (size_t c = 0; c < 3; c++) { + // Check for added edge using this corner. + const CornerIndex ci = corner_table.FirstCorner(face_index) + c; + const CornerIndex co = corner_table.Opposite(ci); + bool is_new_edge = IsNewEdge(corner_table, ci); + + // Check for the new edge using the opposite corner. + if (!is_new_edge && co != kInvalidCornerIndex) { + is_new_edge = IsNewEdge(corner_table, co); + } + // The new edge may become a boundary edge when a degenerate triangle + // created by polygon triangulation is removed by Draco encoder, hence |co| + // is checked below. This can happen when an isolated (boundary) quad only + // has three distinct vertex positions. + // + // TODO(vytyaz): Fix polygon reconstruction with other possible cases of + // degenerate triangles. There are two known sources of degenerate triangles + // that affect polygon reconstruction: + // + // 1. Degenerate triangles created during polygon triangulation are removed + // by Draco encoder, which invalidates the "added_edges" attribute. + // Solution is to discard those triangles before creating the attribute. + // + // 2. Degenerate triangles created by position quantization are encoded and + // decoded by Draco, but not captured into the |corner_table|, causing a + // mismatch between the corner table and the "added_edges" attribute. + // Solution is to use corner table from draco::MeshDecoder here. + // + if (is_new_edge && co != kInvalidCornerIndex) { + // Visit triangle across the new edge. + const FaceIndex opposite_face_index = corner_table.Face(co); + FindOriginalFaceEdges(opposite_face_index, corner_table, triangle_visited, + polygon_edges); + } else { + // Insert the original edge to the map. + const PointIndex point_from = face[(c + 1) % 3]; + const PointIndex point_to = face[(c + 2) % 3]; + polygon_edges->insert( + {PositionIndex(pos_att_->mapped_index(point_from)), point_to}); + } + } +} + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.h index 509d39baf..1d67b5306 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder.h @@ -18,6 +18,7 @@ #include #include "draco/core/encoder_buffer.h" +#include "draco/mesh/corner_table.h" #include "draco/mesh/mesh.h" namespace draco { @@ -44,19 +45,30 @@ class ObjEncoder { bool ExitAndCleanup(bool return_value); private: + typedef AttributeValueIndex PositionIndex; + typedef std::map PolygonEdges; + bool GetAddedEdges(); bool GetSubObjects(); bool EncodeMaterialFileName(); bool EncodePositions(); bool EncodeTextureCoordinates(); bool EncodeNormals(); bool EncodeFaces(); + bool EncodePolygonalFaces(); + bool EncodeFaceAttributes(FaceIndex face_id); bool EncodeSubObject(FaceIndex face_id); bool EncodeMaterial(FaceIndex face_id); bool EncodeFaceCorner(FaceIndex face_id, int local_corner_id); + bool EncodeFaceCorner(PointIndex vert_index); void EncodeFloat(float val); void EncodeFloatList(float *vals, int num_vals); void EncodeInt(int32_t val); + bool IsNewEdge(const CornerTable &ct, CornerIndex ci) const; + void FindOriginalFaceEdges(FaceIndex face_index, + const CornerTable &corner_table, + std::vector *triangle_visited, + PolygonEdges *polygon_edges); // Various attributes used by the encoder. If an attribute is not used, it is // set to nullptr. @@ -66,6 +78,9 @@ class ObjEncoder { const PointAttribute *material_att_; const PointAttribute *sub_obj_att_; + // Stores per-corner triangulation information for polygon reconstruction. + const PointAttribute *added_edges_att_; + // Buffer used for encoding float/int numbers. char num_buffer_[20]; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder_test.cc index 4838e56ca..782983fad 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/obj_encoder_test.cc @@ -16,10 +16,12 @@ #include +#include "draco/attributes/geometry_attribute.h" #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/io/file_reader_factory.h" #include "draco/io/file_reader_interface.h" +#include "draco/io/file_utils.h" #include "draco/io/obj_decoder.h" namespace draco { @@ -27,6 +29,8 @@ namespace draco { class ObjEncoderTest : public ::testing::Test { protected: void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_NE(mesh0, nullptr); + ASSERT_NE(mesh1, nullptr); ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); for (size_t att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { @@ -107,4 +111,34 @@ TEST_F(ObjEncoderTest, TestObjEncodingAll) { test_encoding("two_faces_312.obj"); } +TEST_F(ObjEncoderTest, TestObjOctagonPreserved) { + // Test verifies that OBJ encoder can reconstruct and encode an octagon. + // Decode triangulated octagon and an extra attribute for reconstruction. + std::unique_ptr mesh = + ReadMeshFromTestFile("octagon_preserved.drc"); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 6); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::GENERIC), 1); + ASSERT_NE(mesh->GetMetadata()->GetAttributeMetadataByStringEntry( + "name", "added_edges"), + nullptr); + + // Reconstruct octagon and encode it into an OBJ file. + draco::ObjEncoder obj_encoder; + ASSERT_TRUE(obj_encoder.EncodeToFile( + *mesh, draco::GetTestTempFileFullPath("encoded.obj"))); + + // Read encoded OBJ file and golden OBJ file contents into buffers. + std::vector data_encoded; + std::vector data_golden; + ASSERT_TRUE( + ReadFileToBuffer(GetTestTempFileFullPath("encoded.obj"), &data_encoded)); + ASSERT_TRUE(ReadFileToBuffer(GetTestFileFullPath("octagon_preserved.obj"), + &data_golden)); + + // Check that encoded OBJ file contents are correct. + ASSERT_EQ(data_encoded.size(), data_golden.size()); + ASSERT_EQ(data_encoded, data_golden); +} + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/parser_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/parser_utils.cc index 12afacff6..378de7378 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/parser_utils.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/parser_utils.cc @@ -203,31 +203,40 @@ void ParseLine(DecoderBuffer *buffer, std::string *out_string) { out_string->clear(); } char c; - bool delim_reached = false; + int num_delims = 0; + char last_delim; while (buffer->Peek(&c)) { - // Check if |c| is a delimeter. We want to parse all delimeters until we - // reach a non-delimeter symbol. (E.g. we want to ignore '\r\n' at the end - // of the line). + // Check if |c| is a delimiter symbol. We want to identify all possible + // delimiters that can occur on different platforms (i.e. we want to detect + // '\r\n', '\r', '\n'). const bool is_delim = (c == '\r' || c == '\n'); - // If |c| is a delimeter or it is a non-delimeter symbol before any - // delimeter was found, we advance the buffer to the next character. - if (is_delim || !delim_reached) { - buffer->Advance(1); + if (is_delim) { + if (num_delims == 0) { + last_delim = c; + } else if (num_delims == 1) { + // We already parsed either '\r' or '\n'. Ensure the new delim symbol is + // '\n' and different from the previous symbol. + if (c == last_delim || c != '\n') { + return; // Same delimiter symbol already processed. + } + } else { + // Too many delimiter symbols. + return; + } + num_delims++; } - if (is_delim) { - // Mark that we found a delimeter symbol. - delim_reached = true; - continue; - } - if (delim_reached) { - // We reached a non-delimeter symbol after a delimeter was already found. + if (!is_delim && num_delims > 0) { + // We reached a non-delimiter symbol after a delimiter was already found. // Stop the parsing. return; } - // Otherwise we put the non-delimeter symbol into the output string. - if (out_string) { + + buffer->Advance(1); + + // We put the non-delimiter symbol into the output string. + if (!is_delim && out_string) { out_string->push_back(c); } } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_decoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_decoder_test.cc index 97977c8cc..1dd70d5cb 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_decoder_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_decoder_test.cc @@ -88,6 +88,7 @@ TEST_F(PlyDecoderTest, TestPlyDecodingAll) { // test_decoding("test_pos_color.ply"); // tested test_decoding("cube_quads.ply"); test_decoding("Box.ply"); + test_decoding("delim_test.ply"); } } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_encoder.cc index 2f6a1a2a8..0fe611f1c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_encoder.cc @@ -143,7 +143,8 @@ bool PlyEncoder::EncodeInternal() { buffer()->Encode(header_str.data(), header_str.length()); // Store point attributes. - for (PointIndex v(0); v < in_point_cloud_->num_points(); ++v) { + const int num_points = in_point_cloud_->num_points(); + for (PointIndex v(0); v < num_points; ++v) { const auto *const pos_att = in_point_cloud_->attribute(pos_att_id); buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(v)), pos_att->byte_stride()); @@ -166,9 +167,13 @@ bool PlyEncoder::EncodeInternal() { buffer()->Encode(static_cast(3)); const auto &f = in_mesh_->face(i); - buffer()->Encode(f[0]); - buffer()->Encode(f[1]); - buffer()->Encode(f[2]); + for (int c = 0; c < 3; ++c) { + if (f[c] >= num_points) { + // Invalid point stored on the |in_mesh_| face. + return false; + } + buffer()->Encode(f[c]); + } if (tex_coord_att_id >= 0) { // Two coordinates for every corner -> 6. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_reader_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_reader_test.cc index 05ff63dd4..9612f6377 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/ply_reader_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/ply_reader_test.cc @@ -39,7 +39,7 @@ TEST_F(PlyReaderTest, TestReader) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); ASSERT_EQ(reader.element(1).num_properties(), 1); @@ -64,14 +64,14 @@ TEST_F(PlyReaderTest, TestReaderAscii) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); const std::string file_name_ascii = "test_pos_color_ascii.ply"; const std::vector data_ascii = ReadPlyFile(file_name_ascii); buf.Init(data_ascii.data(), data_ascii.size()); PlyReader reader_ascii; status = reader_ascii.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), reader_ascii.num_elements()); ASSERT_EQ(reader.element(0).num_properties(), reader_ascii.element(0).num_properties()); @@ -96,7 +96,7 @@ TEST_F(PlyReaderTest, TestReaderExtraWhitespace) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); @@ -122,7 +122,7 @@ TEST_F(PlyReaderTest, TestReaderMoreDataTypes) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.cc new file mode 100644 index 000000000..e41d2e1fa --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.cc @@ -0,0 +1,127 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/scene_io.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/io/file_utils.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/obj_encoder.h" +#include "draco/io/ply_encoder.h" + +namespace draco { + +enum SceneFileFormat { UNKNOWN, GLTF, USD, PLY, OBJ }; + +SceneFileFormat GetSceneFileFormat(const std::string &file_name) { + const std::string extension = LowercaseFileExtension(file_name); + if (extension == "gltf" || extension == "glb") { + return GLTF; + } + if (extension == "usd" || extension == "usda" || extension == "usdc" || + extension == "usdz") { + return USD; + } + if (extension == "obj") { + return OBJ; + } + if (extension == "ply") { + return PLY; + } + return UNKNOWN; +} + +StatusOr> ReadSceneFromFile( + const std::string &file_name) { + return ReadSceneFromFile(file_name, nullptr); +} + +StatusOr> ReadSceneFromFile( + const std::string &file_name, std::vector *scene_files) { + std::unique_ptr scene(new Scene()); + switch (GetSceneFileFormat(file_name)) { + case GLTF: { + GltfDecoder decoder; + return decoder.DecodeFromFileToScene(file_name, scene_files); + } + case USD: { + return Status(Status::DRACO_ERROR, "USD is not supported yet."); + } + default: { + return Status(Status::DRACO_ERROR, "Unknown input file format."); + } + } +} + +Status WriteSceneToFile(const std::string &file_name, const Scene &scene) { + Options options; + return WriteSceneToFile(file_name, scene, options); +} + +Status WriteSceneToFile(const std::string &file_name, const Scene &scene, + const Options &options) { + const std::string extension = LowercaseFileExtension(file_name); + std::string folder_path; + std::string out_file_name; + draco::SplitPath(file_name, &folder_path, &out_file_name); + const auto format = GetSceneFileFormat(file_name); + switch (format) { + case GLTF: { + GltfEncoder encoder; + if (!encoder.EncodeToFile(scene, file_name, folder_path)) { + return Status(Status::DRACO_ERROR, "Failed to encode the scene."); + } + return OkStatus(); + } + case USD: { + return Status(Status::DRACO_ERROR, "USD is not supported yet."); + } + case PLY: + case OBJ: { + // Convert the scene to mesh and save the scene as a mesh. For now we do + // that by converting the scene to GLB and decoding the GLB into a mesh. + GltfEncoder gltf_encoder; + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(gltf_encoder.EncodeToBuffer(scene, &buffer)); + GltfDecoder gltf_decoder; + DecoderBuffer dec_buffer; + dec_buffer.Init(buffer.data(), buffer.size()); + DRACO_ASSIGN_OR_RETURN(auto mesh, + gltf_decoder.DecodeFromBuffer(&dec_buffer)); + if (format == PLY) { + PlyEncoder ply_encoder; + if (!ply_encoder.EncodeToFile(*mesh, file_name)) { + return ErrorStatus("Failed to encode the scene as PLY."); + } + } + if (format == OBJ) { + ObjEncoder obj_encoder; + if (!obj_encoder.EncodeToFile(*mesh, file_name)) { + return ErrorStatus("Failed to encode the scene as OBJ."); + } + } + return OkStatus(); + } + default: { + return Status(Status::DRACO_ERROR, "Unknown output file format."); + } + } +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.h b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.h new file mode 100644 index 000000000..964faac3c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io.h @@ -0,0 +1,55 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_SCENE_IO_H_ +#define DRACO_IO_SCENE_IO_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/options.h" +#include "draco/core/status_or.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Reads a scene from a file. Currently only GLTF 2.0 scene files are supported. +// The second form returns the files associated with the scene via the +// |scene_files| argument. +StatusOr> ReadSceneFromFile( + const std::string &file_name); +StatusOr> ReadSceneFromFile( + const std::string &file_name, std::vector *scene_files); + +// Writes a scene into a file. +Status WriteSceneToFile(const std::string &file_name, const Scene &scene); + +// Writes a scene into a file, configurable with |options|. +// +// Supported options: +// +// force_usd_vertex_interpolation= - forces implicit vertex +// interpolation while exporting to USD +// (default = false) +// +Status WriteSceneToFile(const std::string &file_name, const Scene &scene, + const Options &options); + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_SCENE_IO_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io_test.cc new file mode 100644 index 000000000..828065693 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/scene_io_test.cc @@ -0,0 +1,86 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/scene_io.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" +#include "draco/io/mesh_io.h" + +namespace { + +TEST(SceneTest, TestSceneIO) { + // A simple test that verifies that the scene is loaded and saved using the + // scene_io.h API. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + draco::StatusOr> maybe_scene = + draco::ReadSceneFromFile(file_name); + ASSERT_TRUE(maybe_scene.status().ok()); + std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.gltf"); + ASSERT_TRUE(draco::WriteSceneToFile(out_file_name, *scene).ok()); + + // Ensure all files related to the scene are saved. + ASSERT_GT(draco::GetFileSize(out_file_name), 0); + ASSERT_GT( + draco::GetFileSize(draco::GetTestTempFileFullPath("CesiumMilkTruck.png")), + 0); + ASSERT_GT(draco::GetFileSize(draco::GetTestTempFileFullPath("buffer0.bin")), + 0); +} + +TEST(SceneTest, TestSaveToPly) { + // A simple test that verifies that a loaded scene can be stored in a PLY file + // format. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr scene, + draco::ReadSceneFromFile(file_name)); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.ply"); + DRACO_ASSERT_OK(draco::WriteSceneToFile(out_file_name, *scene)); + + // Verify that we can read the saved mesh. + DRACO_ASSIGN_OR_ASSERT(auto mesh, draco::ReadMeshFromFile(out_file_name)); + ASSERT_NE(mesh, nullptr); +} + +TEST(SceneTest, TestSaveToObj) { + // A simple test that verifies that a loaded scene can be stored in an OBJ + // file format. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr scene, + draco::ReadSceneFromFile(file_name)); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.obj"); + DRACO_ASSERT_OK(draco::WriteSceneToFile(out_file_name, *scene)); + + // Verify that we can read the saved mesh. + DRACO_ASSIGN_OR_ASSERT(auto mesh, draco::ReadMeshFromFile(out_file_name)); + ASSERT_NE(mesh, nullptr); +} + +} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stdio_file_reader_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/stdio_file_reader_test.cc index 487819a02..212945f90 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/io/stdio_file_reader_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stdio_file_reader_test.cc @@ -9,7 +9,7 @@ namespace { TEST(StdioFileReaderTest, FailOpen) { EXPECT_EQ(StdioFileReader::Open(""), nullptr); - EXPECT_EQ(StdioFileReader::Open("fake file"), nullptr); + EXPECT_EQ(StdioFileReader::Open("stdio reader fake file"), nullptr); } TEST(StdioFileReaderTest, Open) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.cc new file mode 100644 index 000000000..1e5d3a938 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.cc @@ -0,0 +1,77 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_decoder.h" + +#include + +#include "draco/core/macros.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" + +namespace draco { + +StatusOr> StlDecoder::DecodeFromFile( + const std::string &file_name) { + std::vector data; + if (!ReadFileToBuffer(file_name, &data)) { + return Status(Status::IO_ERROR, "Unable to read input file."); + } + DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + return DecodeFromBuffer(&buffer); +} + +StatusOr> StlDecoder::DecodeFromBuffer( + DecoderBuffer *buffer) { + if (!strncmp(buffer->data_head(), "solid ", 6)) { + return Status(Status::IO_ERROR, + "Currently only binary STL files are supported."); + } + buffer->Advance(80); + uint32_t face_count; + buffer->Decode(&face_count, 4); + + TriangleSoupMeshBuilder builder; + builder.Start(face_count); + + const int32_t pos_att_id = + builder.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); + const int32_t norm_att_id = + builder.AddAttribute(GeometryAttribute::NORMAL, 3, DT_FLOAT32); + + for (uint32_t i = 0; i < face_count; i++) { + float data[48]; + buffer->Decode(data, 48); + uint16_t unused; + buffer->Decode(&unused, 2); + + builder.SetPerFaceAttributeValueForFace( + norm_att_id, draco::FaceIndex(i), + draco::Vector3f(data[0], data[1], data[2]).data()); + + builder.SetAttributeValuesForFace( + pos_att_id, draco::FaceIndex(i), + draco::Vector3f(data[3], data[4], data[5]).data(), + draco::Vector3f(data[6], data[7], data[8]).data(), + draco::Vector3f(data[9], data[10], data[11]).data()); + } + + std::unique_ptr mesh = builder.Finalize(); + return mesh; +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.h new file mode 100644 index 000000000..44b35d849 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder.h @@ -0,0 +1,38 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_STL_DECODER_H_ +#define DRACO_IO_STL_DECODER_H_ + +#include + +#include "draco/core/decoder_buffer.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/draco_features.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Decodes an STL file into draco::Mesh (or draco::PointCloud if the +// connectivity data is not needed). +class StlDecoder { + public: + StatusOr> DecodeFromFile(const std::string &file_name); + StatusOr> DecodeFromBuffer(DecoderBuffer *buffer); +}; + +} // namespace draco + +#endif // DRACO_IO_STL_DECODER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder_test.cc new file mode 100644 index 000000000..886881925 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_decoder_test.cc @@ -0,0 +1,49 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_decoder.h" + +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { + +class StlDecoderTest : public ::testing::Test { + protected: + void test_decoding(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + StlDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr mesh, + decoder.DecodeFromFile(path)); + ASSERT_GT(mesh->num_faces(), 0); + ASSERT_GT(mesh->num_points(), 0); + } + + void test_decoding_should_fail(const std::string &file_name) { + StlDecoder decoder; + StatusOr> statusOrMesh = + decoder.DecodeFromFile(GetTestFileFullPath(file_name)); + ASSERT_FALSE(statusOrMesh.ok()); + } +}; + +TEST_F(StlDecoderTest, TestStlDecoding) { + test_decoding("STL/bunny.stl"); + test_decoding("STL/test_sphere.stl"); + test_decoding_should_fail("STL/test_sphere_ascii.stl"); +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.cc new file mode 100644 index 000000000..5aa4a0a97 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.cc @@ -0,0 +1,111 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_encoder.h" + +#include +#include +#include +#include + +#include "draco/io/file_writer_factory.h" +#include "draco/io/file_writer_interface.h" + +namespace draco { + +StlEncoder::StlEncoder() + : out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr) {} + +Status StlEncoder::EncodeToFile(const Mesh &mesh, + const std::string &file_name) { + in_mesh_ = &mesh; + std::unique_ptr file = + FileWriterFactory::OpenWriter(file_name); + if (!file) { + return Status(Status::IO_ERROR, "File couldn't be opened"); + } + // Encode the mesh into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(mesh, &buffer)); + // Write the buffer into the file. + file->Write(buffer.data(), buffer.size()); + return OkStatus(); +} + +Status StlEncoder::EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer) { + in_mesh_ = &mesh; + out_buffer_ = out_buffer; + Status s = EncodeInternal(); + in_mesh_ = nullptr; // cleanup + in_point_cloud_ = nullptr; + out_buffer_ = nullptr; + return s; +} + +Status StlEncoder::EncodeInternal() { + // Write STL header. + std::stringstream out; + out << std::left << std::setw(80) + << "generated using Draco"; // header is 80 bytes fixed size. + const std::string header_str = out.str(); + buffer()->Encode(header_str.data(), header_str.length()); + + uint32_t num_faces = in_mesh_->num_faces(); + buffer()->Encode(&num_faces, 4); + + std::vector stl_face; + + const int pos_att_id = + in_mesh_->GetNamedAttributeId(GeometryAttribute::POSITION); + + if (pos_att_id < 0) { + return ErrorStatus("Mesh is missing the position attribute."); + } + + if (in_mesh_->attribute(pos_att_id)->data_type() != DT_FLOAT32) { + return ErrorStatus("Mesh position attribute is not of type float32."); + } + + uint16_t unused = 0; + + if (in_mesh_) { + for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) { + const auto &f = in_mesh_->face(i); + const auto *const pos_att = in_mesh_->attribute(pos_att_id); + + // The normal attribute can contain arbitrary normals that may not + // correspond to the winding of the face. + // Therefor we simply always calculate them + // using the points of the triangle face: norm(cross(p2-p1, p3-p1)) + + Vector3f pos[3]; + pos_att->GetMappedValue(f[0], &pos[0][0]); + pos_att->GetMappedValue(f[1], &pos[1][0]); + pos_att->GetMappedValue(f[2], &pos[2][0]); + Vector3f norm = CrossProduct(pos[1] - pos[0], pos[2] - pos[0]); + norm.Normalize(); + buffer()->Encode(norm.data(), sizeof(float) * 3); + + for (int c = 0; c < 3; ++c) { + buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(f[c])), + pos_att->byte_stride()); + } + + buffer()->Encode(&unused, 2); + } + } + return OkStatus(); +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.h b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.h new file mode 100644 index 000000000..8b185b738 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder.h @@ -0,0 +1,52 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_STL_ENCODER_H_ +#define DRACO_IO_STL_ENCODER_H_ + +#include + +#include "draco/core/encoder_buffer.h" +#include "draco/draco_features.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Class for encoding draco::Mesh into the STL file format. +class StlEncoder { + public: + StlEncoder(); + + // Encodes the mesh and saves it into a file. + // Returns false when either the encoding failed or when the file couldn't be + // opened. + Status EncodeToFile(const Mesh &mesh, const std::string &file_name); + + // Encodes the mesh into a buffer. + Status EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer); + + protected: + Status EncodeInternal(); + EncoderBuffer *buffer() const { return out_buffer_; } + + private: + EncoderBuffer *out_buffer_; + + const PointCloud *in_point_cloud_; + const Mesh *in_mesh_; +}; + +} // namespace draco + +#endif // DRACO_IO_STL_ENCODER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder_test.cc new file mode 100644 index 000000000..da6298d64 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/stl_encoder_test.cc @@ -0,0 +1,78 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_encoder.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_reader_factory.h" +#include "draco/io/file_reader_interface.h" +#include "draco/io/stl_decoder.h" + +namespace draco { + +class StlEncoderTest : public ::testing::Test { + protected: + void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); + ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); + for (size_t att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { + ASSERT_EQ(mesh0->attribute(att_id)->size(), + mesh1->attribute(att_id)->size()); + } + } + + // Encode a mesh using the StlEncoder and then decode to verify the encoding. + std::unique_ptr EncodeAndDecodeMesh(const Mesh *mesh) { + EncoderBuffer encoder_buffer; + StlEncoder encoder; + Status status = encoder.EncodeToBuffer(*mesh, &encoder_buffer); + if (!status.ok()) { + return nullptr; + } + + DecoderBuffer decoder_buffer; + decoder_buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + StlDecoder decoder; + StatusOr> status_or_mesh = + decoder.DecodeFromBuffer(&decoder_buffer); + if (!status_or_mesh.ok()) { + return nullptr; + } + std::unique_ptr decoded_mesh = std::move(status_or_mesh).value(); + return decoded_mesh; + } + + void test_encoding(const std::string &file_name) { + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name, true)); + + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + ASSERT_GT(mesh->num_faces(), 0); + + const std::unique_ptr decoded_mesh = EncodeAndDecodeMesh(mesh.get()); + CompareMeshes(mesh.get(), decoded_mesh.get()); + } +}; + +TEST_F(StlEncoderTest, TestStlEncoding) { + // Test decoded mesh from encoded stl file stays the same. + test_encoding("STL/bunny.stl"); + test_encoding("STL/test_sphere.stl"); +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.cc new file mode 100644 index 000000000..cbe2915b0 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.cc @@ -0,0 +1,94 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/texture_io.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/io/file_utils.h" + +namespace draco { + +namespace { + +StatusOr> CreateDracoTextureInternal( + const std::vector &image_data, SourceImage *out_source_image) { + std::unique_ptr draco_texture(new Texture()); + out_source_image->MutableEncodedData() = image_data; + return std::move(draco_texture); +} + +} // namespace + +StatusOr> ReadTextureFromFile( + const std::string &file_name) { + std::vector image_data; + if (!ReadFileToBuffer(file_name, &image_data)) { + return Status(Status::IO_ERROR, "Unable to read input texture file."); + } + + SourceImage source_image; + DRACO_ASSIGN_OR_RETURN(auto texture, + CreateDracoTextureInternal(image_data, &source_image)); + source_image.set_filename(file_name); + const std::string extension = LowercaseFileExtension(file_name); + const std::string mime_type = + "image/" + (extension == "jpg" ? "jpeg" : extension); + source_image.set_mime_type(mime_type); + texture->set_source_image(source_image); + return texture; +} + +StatusOr> ReadTextureFromBuffer( + const uint8_t *buffer, size_t buffer_size, const std::string &mime_type) { + SourceImage source_image; + std::vector image_data(buffer, buffer + buffer_size); + DRACO_ASSIGN_OR_RETURN(auto texture, + CreateDracoTextureInternal(image_data, &source_image)); + source_image.set_mime_type(mime_type); + texture->set_source_image(source_image); + return texture; +} + +Status WriteTextureToFile(const std::string &file_name, + const Texture &texture) { + std::vector buffer; + DRACO_RETURN_IF_ERROR(WriteTextureToBuffer(texture, &buffer)); + + if (!WriteBufferToFile(buffer.data(), buffer.size(), file_name)) { + return Status(Status::DRACO_ERROR, "Failed to write image."); + } + + return OkStatus(); +} + +Status WriteTextureToBuffer(const Texture &texture, + std::vector *buffer) { + // Copy data from the encoded source image if possible, otherwise load the + // data from the source file. + if (!texture.source_image().encoded_data().empty()) { + *buffer = texture.source_image().encoded_data(); + } else if (!texture.source_image().filename().empty()) { + if (!ReadFileToBuffer(texture.source_image().filename(), buffer)) { + return Status(Status::IO_ERROR, "Unable to read input texture file."); + } + } else { + return Status(Status::DRACO_ERROR, "Invalid source data for the texture."); + } + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.h b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.h new file mode 100644 index 000000000..4dbea7554 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io.h @@ -0,0 +1,56 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_TEXTURE_IO_H_ +#define DRACO_IO_TEXTURE_IO_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/draco_types.h" +#include "draco/core/status_or.h" +#include "draco/texture/texture.h" + +namespace draco { + +// Reads a texture from a file. Reads PNG, JPEG and WEBP texture files. +// Returns nullptr with an error status if the decoding failed. +StatusOr> ReadTextureFromFile( + const std::string &file_name); + +// Same as ReadTextureFromFile() but the texture data is parsed from a |buffer|. +// |mime_type| should be set to a type of the texture encoded in |buffer|. +// Supported mime types are "image/jpeg", "image/png" and "image/webp". +// TODO(ostava): We should be able to get the mime type directly from the +// |buffer| but our image decoding library doesn't support this at this time. +StatusOr> ReadTextureFromBuffer( + const uint8_t *buffer, size_t buffer_size, const std::string &mime_type); + +// Writes a texture into a file. Can write PNG, JPEG, WEBP, and KTX2 (with Basis +// compression) texture files depending on the extension specified in +// |file_name| and image format specified in |texture|. Note that images with +// Basis compression can only be saved to files in KTX2 format and not to files +// with "basis" extension. Returns an error status if the writing failed. +Status WriteTextureToFile(const std::string &file_name, const Texture &texture); + +// Writes a |texture| into |buffer|. +Status WriteTextureToBuffer(const Texture &texture, + std::vector *buffer); + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_TEXTURE_IO_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io_test.cc new file mode 100644 index 000000000..13f36e44a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/texture_io_test.cc @@ -0,0 +1,55 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/texture_io.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" + +namespace { + +// Tests loading of textures from a buffer. +TEST(TextureIoTest, TestLoadFromBuffer) { + const std::string file_name = draco::GetTestFileFullPath("test.png"); + std::vector image_data; + ASSERT_TRUE(draco::ReadFileToBuffer(file_name, &image_data)); + + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr texture, + draco::ReadTextureFromBuffer(image_data.data(), image_data.size(), + "image/png")); + ASSERT_NE(texture, nullptr); + + ASSERT_EQ(texture->source_image().mime_type(), "image/png"); + + // Re-encode the texture again to ensure the content hasn't changed. + std::vector encoded_buffer; + DRACO_ASSERT_OK(draco::WriteTextureToBuffer(*texture, &encoded_buffer)); + + ASSERT_EQ(image_data.size(), encoded_buffer.size()); + for (int i = 0; i < encoded_buffer.size(); ++i) { + ASSERT_EQ(image_data[i], encoded_buffer[i]); + } +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.cc new file mode 100644 index 000000000..d57e1093c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.cc @@ -0,0 +1,230 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/tiny_gltf_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/animation/animation.h" +#include "draco/animation/node_animation_data.h" +#include "draco/core/status.h" +#include "draco/core/vector_d.h" +#include "tiny_gltf.h" + +namespace draco { + +int TinyGltfUtils::GetNumComponentsForType(int type) { + switch (type) { + case TINYGLTF_TYPE_SCALAR: + return 1; + case TINYGLTF_TYPE_VEC2: + return 2; + case TINYGLTF_TYPE_VEC3: + return 3; + case TINYGLTF_TYPE_VEC4: + case TINYGLTF_TYPE_MAT2: + return 4; + case TINYGLTF_TYPE_MAT3: + return 9; + case TINYGLTF_TYPE_MAT4: + return 16; + } + return 0; +} + +Material::TransparencyMode TinyGltfUtils::TextToMaterialMode( + const std::string &mode) { + if (mode == "MASK") { + return Material::TRANSPARENCY_MASK; + } else if (mode == "BLEND") { + return Material::TRANSPARENCY_BLEND; + } else { + return Material::TRANSPARENCY_OPAQUE; + } +} + +AnimationSampler::SamplerInterpolation +TinyGltfUtils::TextToSamplerInterpolation(const std::string &interpolation) { + if (interpolation == "STEP") { + return AnimationSampler::SamplerInterpolation::STEP; + } else if (interpolation == "CUBICSPLINE") { + return AnimationSampler::SamplerInterpolation::CUBICSPLINE; + } else { + return AnimationSampler::SamplerInterpolation::LINEAR; + } +} + +AnimationChannel::ChannelTransformation +TinyGltfUtils::TextToChannelTransformation(const std::string &path) { + if (path == "rotation") { + return AnimationChannel::ChannelTransformation::ROTATION; + } else if (path == "scale") { + return AnimationChannel::ChannelTransformation::SCALE; + } else if (path == "weights") { + return AnimationChannel::ChannelTransformation::WEIGHTS; + } else { + return AnimationChannel::ChannelTransformation::TRANSLATION; + } +} + +Status TinyGltfUtils::AddChannelToAnimation( + const tinygltf::Model &model, const tinygltf::Animation &input_animation, + const tinygltf::AnimationChannel &channel, int node_index, + Animation *animation) { + std::unique_ptr new_channel(new AnimationChannel()); + + const tinygltf::AnimationSampler &sampler = + input_animation.samplers[channel.sampler]; + // Add the sampler associated with the channel. + DRACO_RETURN_IF_ERROR( + TinyGltfUtils::AddSamplerToAnimation(model, sampler, animation)); + new_channel->sampler_index = animation->NumSamplers() - 1; + new_channel->target_index = node_index; + new_channel->transformation_type = + TinyGltfUtils::TextToChannelTransformation(channel.target_path); + + animation->AddChannel(std::move(new_channel)); + return OkStatus(); +} + +Status TinyGltfUtils::AddSamplerToAnimation( + const tinygltf::Model &model, const tinygltf::AnimationSampler &sampler, + Animation *animation) { + std::unique_ptr node_animation_data( + new NodeAnimationData()); + // TODO(fgalligan): Add support to not copy the accessor data if it is + // referenced more than once. Currently we duplicate all animation data so + // that it is referenced only once in the glTF file. + const tinygltf::Accessor &input_accessor = model.accessors[sampler.input]; + DRACO_RETURN_IF_ERROR(AddAccessorToAnimationData(model, input_accessor, + node_animation_data.get())); + animation->AddNodeAnimationData(std::move(node_animation_data)); + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->input_index = animation->NumNodeAnimationData() - 1; + + node_animation_data.reset(new NodeAnimationData()); + const tinygltf::Accessor &output_accessor = model.accessors[sampler.output]; + DRACO_RETURN_IF_ERROR(AddAccessorToAnimationData(model, output_accessor, + node_animation_data.get())); + animation->AddNodeAnimationData(std::move(node_animation_data)); + new_sampler->output_index = animation->NumNodeAnimationData() - 1; + + new_sampler->interpolation_type = + TinyGltfUtils::TextToSamplerInterpolation(sampler.interpolation); + animation->AddSampler(std::move(new_sampler)); + return OkStatus(); +} + +// Specialization for returning the data from |accessor| as a vector of float. +template <> +StatusOr> TinyGltfUtils::CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != 1) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); +} + +// Specialization for returing the data from |accessor| as a vector of +// Matrix4x4. +template <> +StatusOr> TinyGltfUtils::CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != 16) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); +} + +Status TinyGltfUtils::AddAccessorToAnimationData( + const tinygltf::Model &model, const tinygltf::Accessor &accessor, + NodeAnimationData *node_animation_data) { + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, + "Unsupported ComponentType for NodeAnimationData."); + } + + std::vector *dest_data = node_animation_data->GetMutableData(); + if (accessor.type == TINYGLTF_TYPE_SCALAR) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + dest_data->push_back(data[i]); + } + node_animation_data->SetType(NodeAnimationData::Type::SCALAR); + } else if (accessor.type == TINYGLTF_TYPE_VEC3) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 3; ++j) { + dest_data->push_back(data[i][j]); + } + } + node_animation_data->SetType(NodeAnimationData::Type::VEC3); + } else if (accessor.type == TINYGLTF_TYPE_VEC4) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 4; ++j) { + dest_data->push_back(data[i][j]); + } + } + node_animation_data->SetType(NodeAnimationData::Type::VEC4); + } else if (accessor.type == TINYGLTF_TYPE_MAT4) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 16; ++j) { + dest_data->push_back(data[i](j)); + } + } + node_animation_data->SetType(NodeAnimationData::Type::MAT4); + } else { + return Status(Status::DRACO_ERROR, + "Unsupported Type for GltfNodeAnimationData."); + } + node_animation_data->SetCount(accessor.count); + node_animation_data->SetNormalized(accessor.normalized); + return OkStatus(); +} + +template <> +void TinyGltfUtils::SetDataImpl(float value, int index, float *values) { + *values = value; +} + +template <> +void TinyGltfUtils::SetDataImpl(float value, int index, + Eigen::Matrix4f *values) { + (*values)(index) = value; +} + +} // namespace draco + +// Actual definitions needed by the tinygltf library using our configuration. +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define TINYGLTF_ENABLE_DRACO +#define TINYGLTF_IMPLEMENTATION + +#include "tiny_gltf.h" + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.h new file mode 100644 index 000000000..a536a70fb --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/io/tiny_gltf_utils.h @@ -0,0 +1,140 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_TINY_GLTF_UTILS_H_ +#define DRACO_IO_TINY_GLTF_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/animation/animation.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/material/material.h" + +#define TINYGLTF_ENCLOSING_NAMESPACE draco +#include "tiny_gltf.h" + +namespace draco { + +class TinyGltfUtils { + public: + TinyGltfUtils() {} + + // Returns the number of components for the attribute type. + static int GetNumComponentsForType(int type); + + // Returns the material transparency mode in |mode|. + static Material::TransparencyMode TextToMaterialMode(const std::string &mode); + + // Returns the animation sampler interpolation in |interpolation|. + static AnimationSampler::SamplerInterpolation TextToSamplerInterpolation( + const std::string &interpolation); + + // Returns the animation channel transformation in |path|. + static AnimationChannel::ChannelTransformation TextToChannelTransformation( + const std::string &path); + + // Adds all of the animation data associated with a channel. + // The channel references a sampler, whose data will be added to the + // |animation|. The sampler references input and output accessors, + // whose data will be added to the |animation|. + static Status AddChannelToAnimation( + const tinygltf::Model &model, const tinygltf::Animation &input_animation, + const tinygltf::AnimationChannel &channel, int node_index, + Animation *animation); + + // Adds all of the sampler data. The sampler references + // input and output accessors, whose data will be added to the |animation|. + static Status AddSamplerToAnimation(const tinygltf::Model &model, + const tinygltf::AnimationSampler &sampler, + Animation *animation); + + // Converts the gltf2 animation accessor and adds it to + // |node_animation_data|. + static Status AddAccessorToAnimationData( + const tinygltf::Model &model, const tinygltf::Accessor &accessor, + NodeAnimationData *node_animation_data); + + // Returns the data from |accessor| as a vector of |T|. + template + static StatusOr> CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != T::dimension) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); + } + + private: + template + static StatusOr> CopyDataAsFloatImpl( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, + "Non-float data is not supported by CopyDataAsFloat()."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, + "Error CopyDataAsFloat() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAsFloat() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const unsigned char *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const int num_components = GetNumComponentsForType(accessor.type); + const unsigned char *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + T values; + + for (int c = 0; c < num_components; ++c) { + float value = 0.0f; + memcpy(&value, data + (c * component_size), component_size); + SetDataImpl(value, c, &values); + } + + output[i] = values; + data += byte_stride; + } + + return output; + } + + template + static void SetDataImpl(float value, int index, T *values) { + (*values)[index] = value; + } +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_TINY_GLTF_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc deleted file mode 100644 index 7e9e6d15d..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#include "draco/javascript/emscripten/animation_decoder_webidl_wrapper.h" - -#include - -#include "draco/compression/decode.h" -#include "draco/mesh/mesh.h" -#include "draco/mesh/mesh_stripifier.h" - -using draco::DecoderBuffer; -using draco::PointAttribute; -using draco::Status; - -DracoFloat32Array::DracoFloat32Array() {} - -float DracoFloat32Array::GetValue(int index) const { return values_[index]; } - -bool DracoFloat32Array::SetValues(const float *values, int count) { - if (values) { - values_.assign(values, values + count); - } else { - values_.resize(count); - } - return true; -} - -AnimationDecoder::AnimationDecoder() {} - -// Decodes animation data from the provided buffer. -const draco::Status *AnimationDecoder::DecodeBufferToKeyframeAnimation( - draco::DecoderBuffer *in_buffer, draco::KeyframeAnimation *animation) { - draco::DecoderOptions dec_options; - last_status_ = decoder_.Decode(dec_options, in_buffer, animation); - return &last_status_; -} - -bool AnimationDecoder::GetTimestamps(const draco::KeyframeAnimation &animation, - DracoFloat32Array *timestamp) { - if (!timestamp) { - return false; - } - const int num_frames = animation.num_frames(); - const draco::PointAttribute *timestamp_att = animation.timestamps(); - // Timestamp attribute has only 1 component, so the number of components is - // equal to the number of frames. - timestamp->SetValues(nullptr, num_frames); - int entry_id = 0; - float timestamp_value = -1.0; - for (draco::PointIndex i(0); i < num_frames; ++i) { - const draco::AttributeValueIndex val_index = timestamp_att->mapped_index(i); - if (!timestamp_att->ConvertValue(val_index, ×tamp_value)) { - return false; - } - timestamp->SetValue(entry_id++, timestamp_value); - } - return true; -} - -bool AnimationDecoder::GetKeyframes(const draco::KeyframeAnimation &animation, - int keyframes_id, - DracoFloat32Array *animation_data) { - const int num_frames = animation.num_frames(); - // Get animation data. - const draco::PointAttribute *animation_data_att = - animation.keyframes(keyframes_id); - if (!animation_data_att) { - return false; - } - - const int components = animation_data_att->num_components(); - const int num_entries = num_frames * components; - const int kMaxAttributeFloatValues = 4; - - std::vector values(components, -1.0); - int entry_id = 0; - animation_data->SetValues(nullptr, num_entries); - for (draco::PointIndex i(0); i < num_frames; ++i) { - const draco::AttributeValueIndex val_index = - animation_data_att->mapped_index(i); - if (!animation_data_att->ConvertValue(val_index, &values[0])) { - return false; - } - for (int j = 0; j < components; ++j) { - animation_data->SetValue(entry_id++, values[j]); - } - } - return true; -} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h deleted file mode 100644 index 7486d1503..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#ifndef DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ -#define DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ - -#include - -#include "draco/animation/keyframe_animation_decoder.h" -#include "draco/attributes/attribute_transform_type.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/config/compression_shared.h" -#include "draco/compression/decode.h" -#include "draco/core/decoder_buffer.h" - -typedef draco::AttributeTransformType draco_AttributeTransformType; -typedef draco::GeometryAttribute draco_GeometryAttribute; -typedef draco_GeometryAttribute::Type draco_GeometryAttribute_Type; -typedef draco::EncodedGeometryType draco_EncodedGeometryType; -typedef draco::Status draco_Status; -typedef draco::Status::Code draco_StatusCode; - -class DracoFloat32Array { - public: - DracoFloat32Array(); - float GetValue(int index) const; - - // In case |values| is nullptr, the data is allocated but not initialized. - bool SetValues(const float *values, int count); - - // Directly sets a value for a specific index. The array has to be already - // allocated at this point (using SetValues() method). - void SetValue(int index, float val) { values_[index] = val; } - - int size() const { return values_.size(); } - - private: - std::vector values_; -}; - -// Class used by emscripten WebIDL Binder [1] to wrap calls to decode animation -// data. -class AnimationDecoder { - public: - AnimationDecoder(); - - // Decodes animation data from the provided buffer. - const draco::Status *DecodeBufferToKeyframeAnimation( - draco::DecoderBuffer *in_buffer, draco::KeyframeAnimation *animation); - - static bool GetTimestamps(const draco::KeyframeAnimation &animation, - DracoFloat32Array *timestamp); - - static bool GetKeyframes(const draco::KeyframeAnimation &animation, - int keyframes_id, DracoFloat32Array *animation_data); - - private: - draco::KeyframeAnimationDecoder decoder_; - draco::Status last_status_; -}; - -#endif // DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc deleted file mode 100644 index 53a10e5e4..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#include "draco/javascript/emscripten/animation_encoder_webidl_wrapper.h" - -#include "draco/animation/keyframe_animation.h" -#include "draco/animation/keyframe_animation_encoder.h" - -DracoInt8Array::DracoInt8Array() {} - -int DracoInt8Array::GetValue(int index) const { return values_[index]; } - -bool DracoInt8Array::SetValues(const char *values, int count) { - values_.assign(values, values + count); - return true; -} - -AnimationBuilder::AnimationBuilder() {} - -bool AnimationBuilder::SetTimestamps(draco::KeyframeAnimation *animation, - long num_frames, const float *timestamps) { - if (!animation || !timestamps) { - return false; - } - std::vector timestamps_arr( - timestamps, timestamps + num_frames); - return animation->SetTimestamps(timestamps_arr); -} - -int AnimationBuilder::AddKeyframes(draco::KeyframeAnimation *animation, - long num_frames, long num_components, - const float *animation_data) { - if (!animation || !animation_data) { - return -1; - } - std::vector keyframes_arr( - animation_data, animation_data + num_frames * num_components); - return animation->AddKeyframes(draco::DT_FLOAT32, num_components, - keyframes_arr); -} - -AnimationEncoder::AnimationEncoder() - : timestamps_quantization_bits_(-1), - keyframes_quantization_bits_(-1), - options_(draco::EncoderOptions::CreateDefaultOptions()) {} - -void AnimationEncoder::SetTimestampsQuantization(long quantization_bits) { - timestamps_quantization_bits_ = quantization_bits; -} - -void AnimationEncoder::SetKeyframesQuantization(long quantization_bits) { - keyframes_quantization_bits_ = quantization_bits; -} - -int AnimationEncoder::EncodeAnimationToDracoBuffer( - draco::KeyframeAnimation *animation, DracoInt8Array *draco_buffer) { - if (!animation) { - return 0; - } - draco::EncoderBuffer buffer; - - if (timestamps_quantization_bits_ > 0) { - options_.SetAttributeInt(0, "quantization_bits", - timestamps_quantization_bits_); - } - if (keyframes_quantization_bits_ > 0) { - for (int i = 1; i <= animation->num_animations(); ++i) { - options_.SetAttributeInt(i, "quantization_bits", - keyframes_quantization_bits_); - } - } - if (!encoder_.EncodeKeyframeAnimation(*animation, options_, &buffer).ok()) { - return 0; - } - - draco_buffer->SetValues(buffer.data(), buffer.size()); - return buffer.size(); -} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h deleted file mode 100644 index f2ac733d1..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#ifndef DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ -#define DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ - -#include - -#include "draco/animation/keyframe_animation_encoder.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/config/compression_shared.h" -#include "draco/compression/config/encoder_options.h" -#include "draco/compression/encode.h" - -class DracoInt8Array { - public: - DracoInt8Array(); - int GetValue(int index) const; - bool SetValues(const char *values, int count); - - size_t size() { return values_.size(); } - - private: - std::vector values_; -}; - -class AnimationBuilder { - public: - AnimationBuilder(); - - bool SetTimestamps(draco::KeyframeAnimation *animation, long num_frames, - const float *timestamps); - - int AddKeyframes(draco::KeyframeAnimation *animation, long num_frames, - long num_components, const float *animation_data); -}; - -class AnimationEncoder { - public: - AnimationEncoder(); - - void SetTimestampsQuantization(long quantization_bits); - // TODO: Use expert encoder to set per attribute quantization. - void SetKeyframesQuantization(long quantization_bits); - int EncodeAnimationToDracoBuffer(draco::KeyframeAnimation *animation, - DracoInt8Array *draco_buffer); - - private: - draco::KeyframeAnimationEncoder encoder_; - long timestamps_quantization_bits_; - long keyframes_quantization_bits_; - draco::EncoderOptions options_; -}; - -#endif // DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc index 66fe77dbd..034f3c3b4 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc @@ -221,14 +221,13 @@ bool Decoder::GetAttributeFloatForAllPoints(const PointCloud &pc, const int components = pa.num_components(); const int num_points = pc.num_points(); const int num_entries = num_points * components; - const int kMaxAttributeFloatValues = 4; - float values[kMaxAttributeFloatValues] = {-2.0, -2.0, -2.0, -2.0}; + std::vector values(components, -2.f); int entry_id = 0; out_values->Resize(num_entries); for (draco::PointIndex i(0); i < num_points; ++i) { const draco::AttributeValueIndex val_index = pa.mapped_index(i); - if (!pa.ConvertValue(val_index, values)) { + if (!pa.ConvertValue(val_index, &values[0])) { return false; } for (int j = 0; j < components; ++j) { @@ -249,17 +248,16 @@ bool Decoder::GetAttributeFloatArrayForAllPoints(const PointCloud &pc, return false; } const bool requested_type_is_float = pa.data_type() == draco::DT_FLOAT32; - const int kMaxAttributeFloatValues = 4; - float values[kMaxAttributeFloatValues] = {-2.0, -2.0, -2.0, -2.0}; + std::vector values(components, -2.f); int entry_id = 0; float *const floats = reinterpret_cast(out_values); for (draco::PointIndex i(0); i < num_points; ++i) { const draco::AttributeValueIndex val_index = pa.mapped_index(i); if (requested_type_is_float) { - pa.GetValue(val_index, values); + pa.GetValue(val_index, &values[0]); } else { - if (!pa.ConvertValue(val_index, values)) { + if (!pa.ConvertValue(val_index, &values[0])) { return false; } } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc deleted file mode 100644 index 83ed98fdc..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is used by emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -#include "draco/attributes/attribute_octahedron_transform.h" -#include "draco/attributes/attribute_quantization_transform.h" -#include "draco/attributes/geometry_attribute.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/decode.h" -#include "draco/core/decoder_buffer.h" -#include "draco/javascript/emscripten/animation_decoder_webidl_wrapper.h" -#include "draco/mesh/mesh.h" -#include "draco/point_cloud/point_cloud.h" - -// glue_animation_decoder.cpp is generated by Makefile.emcc build_glue target. -#include "glue_animation_decoder.cpp" diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl deleted file mode 100644 index c9fe76b59..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl +++ /dev/null @@ -1,52 +0,0 @@ -// Interface exposed to emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -[Prefix="draco::"] -interface DecoderBuffer { - void DecoderBuffer(); - void Init([Const] byte[] data, unsigned long data_size); -}; - -enum draco_StatusCode { - "draco_Status::OK", - "draco_Status::DRACO_ERROR", - "draco_Status::IO_ERROR", - "draco_Status::INVALID_PARAMETER", - "draco_Status::UNSUPPORTED_VERSION", - "draco_Status::UNKNOWN_VERSION", -}; - -[Prefix="draco::"] -interface Status { - draco_StatusCode code(); - boolean ok(); - [Const] DOMString error_msg(); -}; - -// Draco version of typed arrays. The memory of these arrays is allocated on the -// emscripten heap. -interface DracoFloat32Array { - void DracoFloat32Array(); - float GetValue(long index); - long size(); -}; - -[Prefix="draco::"] -interface KeyframeAnimation { - void KeyframeAnimation(); - long num_frames(); - long num_animations(); -}; - -interface AnimationDecoder { - void AnimationDecoder(); - - [Const] Status DecodeBufferToKeyframeAnimation(DecoderBuffer in_buffer, - KeyframeAnimation animation); - - boolean GetTimestamps([Ref, Const] KeyframeAnimation animation, - DracoFloat32Array timestamp); - - boolean GetKeyframes([Ref, Const] KeyframeAnimation animation, - long keyframes_id, - DracoFloat32Array animation_data); -}; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl deleted file mode 100644 index e74a4c9e4..000000000 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl +++ /dev/null @@ -1,34 +0,0 @@ -// Interface exposed to emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -// Draco version of typed arrays. The memory of these arrays is allocated on the -// emscripten heap. -interface DracoInt8Array { - void DracoInt8Array(); - long GetValue(long index); - long size(); -}; - -[Prefix="draco::"] -interface KeyframeAnimation { - void KeyframeAnimation(); - long num_frames(); -}; - -interface AnimationBuilder { - void AnimationBuilder(); - boolean SetTimestamps(KeyframeAnimation animation, long num_frames, - [Const] float[] timestamps); - - long AddKeyframes(KeyframeAnimation animation, long num_frames, - long num_components, [Const] float[] animation_data); -}; - -interface AnimationEncoder { - void AnimationEncoder(); - - void SetTimestampsQuantization(long quantization_bits); - void SetKeyframesQuantization(long quantization_bits); - - long EncodeAnimationToDracoBuffer(KeyframeAnimation animation, - DracoInt8Array encoded_data); -}; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/version.js b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/version.js index 46fb25271..b21f3b5e2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/version.js +++ b/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/version.js @@ -19,7 +19,7 @@ function isVersionSupported(versionString) { const version = versionString.split('.'); if (version.length < 2 || version.length > 3) return false; // Unexpected version string. - if (version[0] == 1 && version[1] >= 0 && version[1] <= 4) + if (version[0] == 1 && version[1] >= 0 && version[1] <= 5) return true; if (version[0] != 0 || version[1] > 10) return false; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material.cc new file mode 100644 index 000000000..da854a172 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material.cc @@ -0,0 +1,258 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +Material::Material() : Material(nullptr) {} + +Material::Material(TextureLibrary *texture_library) + : texture_library_(texture_library) { + Clear(); +} + +void Material::Copy(const Material &src) { + name_ = src.name_; + color_factor_ = src.color_factor_; + metallic_factor_ = src.metallic_factor_; + roughness_factor_ = src.roughness_factor_; + emissive_factor_ = src.emissive_factor_; + transparency_mode_ = src.transparency_mode_; + alpha_cutoff_ = src.alpha_cutoff_; + double_sided_ = src.double_sided_; + normal_texture_scale_ = src.normal_texture_scale_; + + // Copy properties of material extensions. + unlit_ = src.unlit_; + has_sheen_ = src.has_sheen_; + sheen_color_factor_ = src.sheen_color_factor_; + sheen_roughness_factor_ = src.sheen_roughness_factor_; + has_transmission_ = src.has_transmission_; + transmission_factor_ = src.transmission_factor_; + has_clearcoat_ = src.has_clearcoat_; + clearcoat_factor_ = src.clearcoat_factor_; + clearcoat_roughness_factor_ = src.clearcoat_roughness_factor_; + has_volume_ = src.has_volume_; + thickness_factor_ = src.thickness_factor_; + attenuation_distance_ = src.attenuation_distance_; + attenuation_color_ = src.attenuation_color_; + has_ior_ = src.has_ior_; + ior_ = src.ior_; + has_specular_ = src.has_specular_; + specular_factor_ = src.specular_factor_; + specular_color_factor_ = src.specular_color_factor_; + + // Copy texture maps. + texture_map_type_to_index_map_ = src.texture_map_type_to_index_map_; + texture_maps_.resize(src.texture_maps_.size()); + for (int i = 0; i < texture_maps_.size(); ++i) { + texture_maps_[i] = std::unique_ptr(new TextureMap()); + texture_maps_[i]->Copy(*src.texture_maps_[i]); + } +} + +void Material::Clear() { + ClearTextureMaps(); + + // Defaults correspond to the GLTF 2.0 spec. + name_.clear(); + color_factor_ = Vector4f(1.f, 1.f, 1.f, 1.f); + metallic_factor_ = 1.f; + roughness_factor_ = 1.f; + emissive_factor_ = Vector3f(0.f, 0.f, 0.f); + transparency_mode_ = TRANSPARENCY_OPAQUE; + alpha_cutoff_ = 0.5f; + double_sided_ = false; + normal_texture_scale_ = 1.0f; + + // Clear properties of material extensions to glTF 2.0 spec defaults. + unlit_ = false; + has_sheen_ = false; + sheen_color_factor_ = Vector3f(0.f, 0.f, 0.f); + sheen_roughness_factor_ = 0.f; + has_transmission_ = false; + transmission_factor_ = 0.f; + has_clearcoat_ = false; + clearcoat_factor_ = 0.f; + clearcoat_roughness_factor_ = 0.f; + has_volume_ = false; + thickness_factor_ = 0.f; + attenuation_distance_ = std::numeric_limits::max(); // Infinity. + attenuation_color_ = Vector3f(1.f, 1.f, 1.f); + has_ior_ = false; + ior_ = 1.5f; + has_specular_ = false; + specular_factor_ = 1.f; + specular_color_factor_ = Vector3f(1.f, 1.f, 1.f); +} + +void Material::ClearTextureMaps() { + texture_maps_.clear(); + texture_map_type_to_index_map_.clear(); +} + +void Material::SetTextureMap(TextureMap &&texture_map) { + std::unique_ptr new_texture_map(new TextureMap); + *new_texture_map = std::move(texture_map); + SetTextureMap(std::move(new_texture_map)); +} + +void Material::SetTextureMap(std::unique_ptr texture_map) { + const TextureMap::Type type = texture_map->type(); + const auto it = texture_map_type_to_index_map_.find(type); + // Only one texture of a given type is allowed to exist. + if (it == texture_map_type_to_index_map_.end()) { + texture_maps_.push_back(std::move(texture_map)); + texture_map_type_to_index_map_[type] = texture_maps_.size() - 1; + } else { + texture_maps_[it->second] = std::move(texture_map); + } +} + +void Material::SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + int tex_coord_index) { + SetTextureMap(std::move(texture), texture_map_type, + TextureMap::WrappingMode(TextureMap::CLAMP_TO_EDGE), + tex_coord_index); +} + +void Material::SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + texture_map->SetProperties(texture_map_type, wrapping_mode, tex_coord_index); + + if (texture_library_) { + texture_map->SetTexture(texture.get()); + texture_library_->PushTexture(std::move(texture)); + } else { + texture_map->SetTexture(std::move(texture)); + } + SetTextureMap(std::move(texture_map)); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + int tex_coord_index) { + return SetTextureMap(texture, texture_map_type, + TextureMap::WrappingMode(TextureMap::CLAMP_TO_EDGE), + TextureMap::UNSPECIFIED, TextureMap::UNSPECIFIED, + tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, TextureMap::UNSPECIFIED, + TextureMap::UNSPECIFIED, tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, min_filter, mag_filter, tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + const TextureTransform &transform, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + texture_map->SetTransform(transform); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, min_filter, mag_filter, tex_coord_index); +} + +Status Material::SetTextureMap(std::unique_ptr texture_map, + Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + int tex_coord_index) { + if (!IsTextureOwned(*texture)) { + return Status(Status::DRACO_ERROR, + "Provided texture is not owned by the material."); + } + texture_map->SetProperties(texture_map_type, wrapping_mode, tex_coord_index, + min_filter, mag_filter); + texture_map->SetTexture(texture); + SetTextureMap(std::move(texture_map)); + return OkStatus(); +} + +bool Material::IsTextureOwned(const Texture &texture) { + if (texture_library_) { + // Ensure the texture is owned by the texture library. + for (int ti = 0; ti < texture_library_->NumTextures(); ++ti) { + if (texture_library_->GetTexture(ti) == &texture) { + return true; + } + } + return false; + } + // Else we need to check every texture map of this material. + for (int ti = 0; ti < NumTextureMaps(); ++ti) { + if (GetTextureMapByIndex(ti)->texture() == &texture) { + return true; + } + } + return false; +} + +std::unique_ptr Material::RemoveTextureMapByIndex(int index) { + if (index < 0 || index >= texture_maps_.size()) { + return nullptr; + } + std::unique_ptr ret = std::move(texture_maps_[index]); + texture_maps_.erase(texture_maps_.begin() + index); + // A texture map was removed and we need to update + // |texture_map_type_to_index_map_| to reflect the changes. + for (int i = index; i < texture_maps_.size(); ++i) { + texture_map_type_to_index_map_[texture_maps_[i]->type()] = i; + } + // Delete the removed texture map type. + texture_map_type_to_index_map_.erase( + texture_map_type_to_index_map_.find(ret->type())); + return ret; +} + +std::unique_ptr Material::RemoveTextureMapByType( + TextureMap::Type texture_type) { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return RemoveTextureMapByIndex(it->second); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material.h b/Engine/lib/assimp/contrib/draco/src/draco/material/material.h new file mode 100644 index 000000000..7c405b45c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material.h @@ -0,0 +1,276 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MATERIAL_MATERIAL_H_ +#define DRACO_MATERIAL_MATERIAL_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/status.h" +#include "draco/core/vector_d.h" +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Material specification for Draco geometry. Parameters are based on the +// metallic-roughness PBR model adopted by GLTF 2.0 standard: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials +class Material { + public: + enum TransparencyMode { + TRANSPARENCY_OPAQUE = 0, + TRANSPARENCY_MASK, + TRANSPARENCY_BLEND + }; + + Material(); + explicit Material(TextureLibrary *texture_library); + + // Copies all material data from the |src| material to this material. + void Copy(const Material &src); + + // Deletes all texture maps and resets all material properties to default + // values. + void Clear(); + + // Deletes all texture maps from the material while keeping other material + // properties unchanged. + void ClearTextureMaps(); + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + Vector4f GetColorFactor() const { return color_factor_; } + void SetColorFactor(const Vector4f &color_factor) { + color_factor_ = color_factor; + } + float GetMetallicFactor() const { return metallic_factor_; } + void SetMetallicFactor(float metallic_factor) { + metallic_factor_ = metallic_factor; + } + float GetRoughnessFactor() const { return roughness_factor_; } + void SetRoughnessFactor(float roughness_factor) { + roughness_factor_ = roughness_factor; + } + Vector3f GetEmissiveFactor() const { return emissive_factor_; } + void SetEmissiveFactor(const Vector3f &emissive_factor) { + emissive_factor_ = emissive_factor; + } + bool GetDoubleSided() const { return double_sided_; } + void SetDoubleSided(bool double_sided) { double_sided_ = double_sided; } + TransparencyMode GetTransparencyMode() const { return transparency_mode_; } + void SetTransparencyMode(TransparencyMode mode) { transparency_mode_ = mode; } + float GetAlphaCutoff() const { return alpha_cutoff_; } + void SetAlphaCutoff(float alpha_cutoff) { alpha_cutoff_ = alpha_cutoff; } + float GetNormalTextureScale() const { return normal_texture_scale_; } + void SetNormalTextureScale(float scale) { normal_texture_scale_ = scale; } + + // Properties of glTF material extension KHR_materials_unlit. + bool GetUnlit() const { return unlit_; } + void SetUnlit(bool unlit) { unlit_ = unlit; } + + // Properties of glTF material extension KHR_materials_sheen. + bool HasSheen() const { return has_sheen_; } + void SetHasSheen(bool value) { has_sheen_ = value; } + Vector3f GetSheenColorFactor() const { return sheen_color_factor_; } + void SetSheenColorFactor(const Vector3f &value) { + sheen_color_factor_ = value; + } + float GetSheenRoughnessFactor() const { return sheen_roughness_factor_; } + void SetSheenRoughnessFactor(float value) { sheen_roughness_factor_ = value; } + + // Properties of glTF material extension KHR_materials_transmission. + bool HasTransmission() const { return has_transmission_; } + void SetHasTransmission(bool value) { has_transmission_ = value; } + float GetTransmissionFactor() const { return transmission_factor_; } + void SetTransmissionFactor(float value) { transmission_factor_ = value; } + + // Properties of glTF material extension KHR_materials_clearcoat. + bool HasClearcoat() const { return has_clearcoat_; } + void SetHasClearcoat(bool value) { has_clearcoat_ = value; } + float GetClearcoatFactor() const { return clearcoat_factor_; } + void SetClearcoatFactor(float value) { clearcoat_factor_ = value; } + float GetClearcoatRoughnessFactor() const { + return clearcoat_roughness_factor_; + } + void SetClearcoatRoughnessFactor(float value) { + clearcoat_roughness_factor_ = value; + } + + // Properties of glTF material extension KHR_materials_volume. + bool HasVolume() const { return has_volume_; } + void SetHasVolume(bool value) { has_volume_ = value; } + float GetThicknessFactor() const { return thickness_factor_; } + void SetThicknessFactor(float value) { thickness_factor_ = value; } + float GetAttenuationDistance() const { return attenuation_distance_; } + void SetAttenuationDistance(float value) { attenuation_distance_ = value; } + Vector3f GetAttenuationColor() const { return attenuation_color_; } + void SetAttenuationColor(const Vector3f &value) { + attenuation_color_ = value; + } + + // Properties of glTF material extension KHR_materials_ior. + bool HasIor() const { return has_ior_; } + void SetHasIor(bool value) { has_ior_ = value; } + float GetIor() const { return ior_; } + void SetIor(float value) { ior_ = value; } + + // Properties of glTF material extension KHR_materials_specular. + bool HasSpecular() const { return has_specular_; } + void SetHasSpecular(bool value) { has_specular_ = value; } + float GetSpecularFactor() const { return specular_factor_; } + void SetSpecularFactor(float value) { specular_factor_ = value; } + Vector3f GetSpecularColorFactor() const { return specular_color_factor_; } + void SetSpecularColorFactor(const Vector3f &value) { + specular_color_factor_ = value; + } + + // Methods for working with texture maps. + size_t NumTextureMaps() const { return texture_maps_.size(); } + const TextureMap *GetTextureMapByIndex(int index) const { + return texture_maps_[index].get(); + } + TextureMap *GetTextureMapByIndex(int index) { + return texture_maps_[index].get(); + } + const TextureMap *GetTextureMapByType(TextureMap::Type texture_type) const { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return GetTextureMapByIndex(it->second); + } + TextureMap *GetTextureMapByType(TextureMap::Type texture_type) { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return GetTextureMapByIndex(it->second); + } + + // TODO(b/146061359): Refactor the set texture map code. + // Specifies a new texture map using a texture with a given type. + // |tex_coord_index| defines which texture coordinate attribute should be used + // to map the texture on the underlying geometry (e.g. tex_coord_index 0 would + // use the first texture coordinate attribute). + void SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, int tex_coord_index); + void SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index); + + // Sets a new texture map using a |texture| that is already owned by this + // material (that is by one of its texture maps or by the unerlying + // |texture_library_|). |transform| is the texture map's transform if set. + // |min_filter| and |mag_filter| are the texture filter types. Returns error + // status if provided |texture| is not owned by the material. + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + const TextureTransform &transform, int tex_coord_index); + + // Removes a texture map from the material based on its index or texture type. + // The material releases the ownership of the texture map and returns it as + // a unique_ptr to allow the caller to use the texture map for other purposes. + std::unique_ptr RemoveTextureMapByIndex(int index); + std::unique_ptr RemoveTextureMapByType( + TextureMap::Type texture_type); + + private: + void SetTextureMap(TextureMap &&texture_map); + void SetTextureMap(std::unique_ptr texture_map); + Status SetTextureMap(std::unique_ptr texture_map, + Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, int tex_coord_index); + + // Returns true if the |texture| is owned by the material. + bool IsTextureOwned(const Texture &texture); + + private: + std::string name_; + Vector4f color_factor_; + float metallic_factor_; + float roughness_factor_; + Vector3f emissive_factor_; + bool double_sided_; + TransparencyMode transparency_mode_; + float alpha_cutoff_; + float normal_texture_scale_; + + // Properties of glTF material extension KHR_materials_unlit. + bool unlit_; + + // Properties of glTF material extension KHR_materials_sheen. + bool has_sheen_; + Vector3f sheen_color_factor_; + float sheen_roughness_factor_; + + // Properties of glTF material extension KHR_materials_transmission. + bool has_transmission_; + float transmission_factor_; + + // Properties of glTF material extension KHR_materials_clearcoat. + bool has_clearcoat_; + float clearcoat_factor_; + float clearcoat_roughness_factor_; + + // Properties of glTF material extension KHR_materials_volume. + bool has_volume_; + float thickness_factor_; + float attenuation_distance_; + Vector3f attenuation_color_; + + // Properties of glTF material extension KHR_materials_ior. + bool has_ior_; + float ior_; + + // Properties of glTF material extension KHR_materials_specular. + bool has_specular_; + float specular_factor_; + Vector3f specular_color_factor_; + + // Texture maps. + std::vector> texture_maps_; + + // Map between a texture type to texture index in |texture_maps_|. Allows fast + // retrieval of texture maps based on their type. + std::unordered_map texture_map_type_to_index_map_; + + // Optional pointer to a library that holds ownership of textures used for + // this material. If set to nullptr, the texture ownership will be assigned + // to the newly created TextureMaps directly. + TextureLibrary *texture_library_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MATERIAL_MATERIAL_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.cc new file mode 100644 index 000000000..f2165295f --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.cc @@ -0,0 +1,125 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_library.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +namespace draco { + +void MaterialLibrary::Copy(const MaterialLibrary &src) { + Clear(); + Append(src); +} + +void MaterialLibrary::Append(const MaterialLibrary &src) { + const size_t old_num_materials = materials_.size(); + materials_.resize(old_num_materials + src.materials_.size()); + for (int i = 0; i < src.materials_.size(); ++i) { + materials_[old_num_materials + i] = + std::unique_ptr(new Material(&texture_library_)); + materials_[old_num_materials + i]->Copy(*src.materials_[i]); + } + + const size_t old_num_textures = texture_library_.NumTextures(); + texture_library_.Append(src.texture_library_); + for (int i = 0; i < src.materials_variants_names_.size(); i++) { + materials_variants_names_.push_back(src.materials_variants_names_[i]); + } + + // Remap all texture maps to the textures in the new texture library. + + // First gather mapping between texture maps and textures in the old material + // library. + const auto texture_map_to_index = + ComputeTextureMapToTextureIndexMapping(src.texture_library_); + + // Remap all texture maps to textures stored in the new texture library. + for (auto it = texture_map_to_index.begin(); it != texture_map_to_index.end(); + ++it) { + TextureMap *const texture_map = it->first; + const int texture_index = old_num_textures + it->second; + texture_map->SetTexture(texture_library_.GetTexture(texture_index)); + } +} + +std::unique_ptr MaterialLibrary::RemoveMaterial(int index) { + std::unique_ptr ret = std::move(materials_[index]); + materials_.erase(materials_.begin() + index); + return ret; +} + +void MaterialLibrary::RemoveUnusedTextures() { + const auto texture_map_to_index = + ComputeTextureMapToTextureIndexMapping(texture_library_); + + // Mark which textures are used. + std::vector is_texture_used(texture_library_.NumTextures(), false); + for (auto it = texture_map_to_index.begin(); it != texture_map_to_index.end(); + ++it) { + is_texture_used[it->second] = true; + } + + // Remove all textures that are not used (from backwards to avoid updating + // entries in the |is_texture_used| vector). + for (int i = texture_library_.NumTextures() - 1; i >= 0; --i) { + if (!is_texture_used[i]) { + texture_library_.RemoveTexture(i); + } + } +} + +std::map +MaterialLibrary::ComputeTextureMapToTextureIndexMapping( + const TextureLibrary &library) const { + std::map map_to_index; + for (int mi = 0; mi < materials_.size(); ++mi) { + for (int ti = 0; ti < materials_[mi]->NumTextureMaps(); ++ti) { + TextureMap *const texture_map = materials_[mi]->GetTextureMapByIndex(ti); + for (int tli = 0; tli < library.NumTextures(); ++tli) { + if (library.GetTexture(tli) != texture_map->texture()) { + continue; + } + map_to_index[texture_map] = tli; + break; + } + } + } + return map_to_index; +} + +void MaterialLibrary::Clear() { + materials_.clear(); + texture_library_.Clear(); + materials_variants_names_.clear(); +} + +Material *MaterialLibrary::MutableMaterial(int index) { + if (index < 0) { + return nullptr; + } + if (materials_.size() <= index) { + const int old_size = materials_.size(); + materials_.resize(index + 1); + // Ensure all newly created materials are valid. + for (int i = old_size; i < index + 1; ++i) { + materials_[i] = + std::unique_ptr(new Material(&texture_library_)); + } + } + return materials_[index].get(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.h b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.h new file mode 100644 index 000000000..574d86b23 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library.h @@ -0,0 +1,104 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MATERIAL_MATERIAL_LIBRARY_H_ +#define DRACO_MATERIAL_MATERIAL_LIBRARY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/material/material.h" +#include "draco/texture/texture_library.h" + +namespace draco { + +// MaterialLibrary holds an array of materials that are applied to a single +// model. +class MaterialLibrary { + public: + MaterialLibrary() = default; + + // Copies the |src| into this instance. + void Copy(const MaterialLibrary &src); + + // Appends materials from the |src| library to this library. All materials + // and textures are copied over. + void Append(const MaterialLibrary &src); + + // Deletes all materials from the material library. + void Clear(); + + // The number of materials stored in the library. All materials are stored + // with indices <0, num_materials() - 1>. + size_t NumMaterials() const { return materials_.size(); } + + // Returns a material with a given index or nullptr if the index is not valid. + const Material *GetMaterial(int index) const { + if (index < 0 || index >= materials_.size()) { + return nullptr; + } + return materials_[index].get(); + } + + // Returns a mutable pointer to a given material. If the material with the + // specified |index| does not exist, it is automatically created. + Material *MutableMaterial(int index); + + // Removes a material with a given index and returns it. Caller can ignore the + // returned value, in which case the material will be automatically deleted. + // Index of all subsequent materials will be decremented by one. + std::unique_ptr RemoveMaterial(int index); + + const TextureLibrary &GetTextureLibrary() const { return texture_library_; } + TextureLibrary &MutableTextureLibrary() { return texture_library_; } + + // Removes all textures that are not referenced by a TextureMap from the + // texture library. + void RemoveUnusedTextures(); + + // Returns a map between each TextureMap object and associated texture index + // in the texture |library|. + std::map ComputeTextureMapToTextureIndexMapping( + const TextureLibrary &library) const; + + // Creates a named materials variant and returns its index. + int AddMaterialsVariant(const std::string &name) { + materials_variants_names_.push_back(name); + return materials_variants_names_.size() - 1; + } + + // Returns the number of materials variants. + int NumMaterialsVariants() const { return materials_variants_names_.size(); } + + // Returns the name of a materials variant. + const std::string &GetMaterialsVariantName(int index) const { + return materials_variants_names_[index]; + } + + private: + std::vector> materials_; + std::vector materials_variants_names_; + + // Container for storing all textures used by materials of this library. + TextureLibrary texture_library_; +}; + +} // namespace draco + +#endif // DRACO_MATERIAL_MATERIAL_LIBRARY_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_library_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library_test.cc new file mode 100644 index 000000000..a110fa4db --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_library_test.cc @@ -0,0 +1,155 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_library.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(MaterialLibraryTest, TestMaterials) { + // Test verifies that we can modify materials in a library. + draco::MaterialLibrary library; + ASSERT_EQ(library.NumMaterials(), 0); + + // Add a new material to the library. + const draco::Material *const new_mat = library.MutableMaterial(0); + ASSERT_NE(new_mat, nullptr); + ASSERT_EQ(library.NumMaterials(), 1); + + const draco::Material *const new_mat2 = library.MutableMaterial(2); + ASSERT_NE(new_mat2, nullptr); + ASSERT_EQ(library.NumMaterials(), 3); + ASSERT_EQ(library.GetMaterial(2), new_mat2); + + // Ensure that even though we call mutable_material multiple times, it does + // not increase the number of materials associated to the library. + for (int i = 0; i < library.NumMaterials(); ++i) { + ASSERT_NE(library.MutableMaterial(i), nullptr); + } + ASSERT_EQ(library.NumMaterials(), 3); + + // Check that material variants can be added and cleared. + library.AddMaterialsVariant("Milk Truck"); + library.AddMaterialsVariant("Ice Cream Truck"); + ASSERT_EQ(library.NumMaterialsVariants(), 2); + ASSERT_EQ(library.GetMaterialsVariantName(0), "Milk Truck"); + ASSERT_EQ(library.GetMaterialsVariantName(1), "Ice Cream Truck"); + + library.Clear(); + ASSERT_EQ(library.NumMaterials(), 0); + ASSERT_EQ(library.NumMaterialsVariants(), 0); +} + +TEST(MaterialLibraryTest, TestMaterialsCopy) { + // Test verifies that we can copy a material library. + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetMetallicFactor(2.4f); + library.MutableMaterial(3)->SetRoughnessFactor(1.2f); + library.AddMaterialsVariant("Milk Truck"); + library.AddMaterialsVariant("Ice Cream Truck"); + + draco::MaterialLibrary new_library; + new_library.Copy(library); + ASSERT_EQ(library.NumMaterials(), new_library.NumMaterials()); + ASSERT_EQ(library.GetMaterial(0)->GetMetallicFactor(), + new_library.GetMaterial(0)->GetMetallicFactor()); + ASSERT_EQ(library.GetMaterial(3)->GetRoughnessFactor(), + new_library.GetMaterial(3)->GetRoughnessFactor()); + ASSERT_EQ(new_library.NumMaterialsVariants(), 2); + ASSERT_EQ(new_library.GetMaterialsVariantName(0), "Milk Truck"); + ASSERT_EQ(new_library.GetMaterialsVariantName(1), "Ice Cream Truck"); +} + +TEST(MaterialLibraryTest, TestTextureLibrary) { + // Tests that texture library is properly updated when we add new textures + // to a material belonging to the material library. + std::unique_ptr texture_0(new draco::Texture()); + std::unique_ptr texture_1(new draco::Texture()); + + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetTextureMap(std::move(texture_0), + draco::TextureMap::COLOR, 0); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 1); + library.MutableMaterial(3)->SetTextureMap(std::move(texture_1), + draco::TextureMap::COLOR, 0); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 2); +} + +TEST(MaterialLibraryTest, RemoveUnusedTextures) { + // Test verifies that we can remove unusued textures from the material + // library. + draco::MaterialLibrary library; + + // Create dummy textures. + std::unique_ptr texture_0(new draco::Texture()); + std::unique_ptr texture_1(new draco::Texture()); + std::unique_ptr texture_2(new draco::Texture()); + + // Add them to the materials of the library. + library.MutableMaterial(0)->SetTextureMap(std::move(texture_0), + draco::TextureMap::COLOR, 0); + library.MutableMaterial(0)->SetTextureMap( + std::move(texture_1), draco::TextureMap::METALLIC_ROUGHNESS, 0); + library.MutableMaterial(1)->SetTextureMap(std::move(texture_2), + draco::TextureMap::COLOR, 0); + + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 3); + + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 3); + + // Remove texture map from a material. + library.MutableMaterial(0)->RemoveTextureMapByType( + draco::TextureMap::METALLIC_ROUGHNESS); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 2); + + library.MutableMaterial(1)->RemoveTextureMapByType(draco::TextureMap::COLOR); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 1); + + library.MutableMaterial(0)->RemoveTextureMapByType(draco::TextureMap::COLOR); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 0); +} + +TEST(MaterialLibraryTest, RemoveMaterial) { + // Tests that we can safely remove materials from the material library. + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetMetallicFactor(0.f); + library.MutableMaterial(1)->SetMetallicFactor(1.f); + library.MutableMaterial(2)->SetMetallicFactor(2.f); + library.MutableMaterial(3)->SetMetallicFactor(3.f); + + ASSERT_EQ(library.NumMaterials(), 4); + + ASSERT_EQ(library.RemoveMaterial(0)->GetMetallicFactor(), 0.f); + ASSERT_EQ(library.NumMaterials(), 3); + + ASSERT_EQ(library.RemoveMaterial(1)->GetMetallicFactor(), 2.f); + ASSERT_EQ(library.NumMaterials(), 2); + + ASSERT_EQ(library.RemoveMaterial(1)->GetMetallicFactor(), 3.f); + ASSERT_EQ(library.NumMaterials(), 1); + + ASSERT_EQ(library.RemoveMaterial(0)->GetMetallicFactor(), 1.f); + ASSERT_EQ(library.NumMaterials(), 0); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material_test.cc new file mode 100644 index 000000000..8c999a532 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_test.cc @@ -0,0 +1,320 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace { + +TEST(MaterialTest, TestMaterialAccess) { + // Tests that we can set and get material properties. + draco::Material material; + + material.SetName("Superalloy"); + ASSERT_EQ(material.GetName(), "Superalloy"); + material.SetColorFactor(draco::Vector4f(1.f, 0.2f, 0.1f, 0.9f)); + ASSERT_EQ(material.GetColorFactor(), draco::Vector4f(1.f, 0.2f, 0.1f, 0.9f)); + material.SetMetallicFactor(0.3f); + ASSERT_EQ(material.GetMetallicFactor(), 0.3f); + material.SetRoughnessFactor(0.2f); + ASSERT_EQ(material.GetRoughnessFactor(), 0.2f); + material.SetEmissiveFactor(draco::Vector3f(0.2f, 0.f, 0.1f)); + ASSERT_EQ(material.GetEmissiveFactor(), draco::Vector3f(0.2f, 0.f, 0.1f)); + + // Set and check the properties of material extensions. + material.SetUnlit(true); + ASSERT_TRUE(material.GetUnlit()); + material.SetHasSheen(true); + ASSERT_TRUE(material.HasSheen()); + material.SetSheenColorFactor(draco::Vector3f(0.4f, 0.2f, 0.8f)); + ASSERT_EQ(material.GetSheenColorFactor(), draco::Vector3f(0.4f, 0.2f, 0.8f)); + material.SetSheenRoughnessFactor(0.428f); + ASSERT_EQ(material.GetSheenRoughnessFactor(), 0.428f); + material.SetHasTransmission(true); + ASSERT_TRUE(material.HasTransmission()); + material.SetTransmissionFactor(0.5f); + ASSERT_EQ(material.GetTransmissionFactor(), 0.5f); + material.SetHasClearcoat(true); + ASSERT_TRUE(material.HasClearcoat()); + material.SetClearcoatFactor(0.6f); + ASSERT_EQ(material.GetClearcoatFactor(), 0.6f); + material.SetClearcoatRoughnessFactor(0.7f); + ASSERT_EQ(material.GetClearcoatRoughnessFactor(), 0.7f); + material.SetHasVolume(true); + ASSERT_TRUE(material.HasVolume()); + material.SetThicknessFactor(0.8f); + ASSERT_EQ(material.GetThicknessFactor(), 0.8f); + material.SetAttenuationDistance(0.9f); + ASSERT_EQ(material.GetAttenuationDistance(), 0.9f); + material.SetAttenuationColor(draco::Vector3f(0.2f, 0.5f, 0.8f)); + ASSERT_EQ(material.GetAttenuationColor(), draco::Vector3f(0.2f, 0.5f, 0.8f)); + material.SetHasIor(true); + ASSERT_TRUE(material.HasIor()); + material.SetIor(1.1f); + ASSERT_EQ(material.GetIor(), 1.1f); + material.SetHasSpecular(true); + ASSERT_TRUE(material.HasSpecular()); + material.SetSpecularFactor(0.01f); + ASSERT_EQ(material.GetSpecularFactor(), 0.01f); + material.SetSpecularColorFactor(draco::Vector3f(0.4f, 1.f, 1.f)); + ASSERT_EQ(material.GetSpecularColorFactor(), draco::Vector3f(0.4f, 1.f, 1.f)); + + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + ASSERT_EQ(material.NumTextureMaps(), 0); + + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_EQ(material.GetTextureMapByIndex(0), + material.GetTextureMapByType(draco::TextureMap::COLOR)); + + std::unique_ptr texture2 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture2, nullptr); + material.SetTextureMap(std::move(texture2), draco::TextureMap::EMISSIVE, 1); + + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE) + ->tex_coord_index(), + 1); + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Try to add the emissive texture one more time. This should replace the + // previous instance of the emissive texture on the material. + std::unique_ptr texture3 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture3, nullptr); + material.SetTextureMap(std::move(texture2), draco::TextureMap::EMISSIVE, 2); + ASSERT_EQ(material.NumTextureMaps(), 2); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE) + ->tex_coord_index(), + 2); + + std::unique_ptr texture_map4(new draco::TextureMap()); + std::unique_ptr texture4 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + material.SetTextureMap(std::move(texture4), draco::TextureMap::ROUGHNESS, 0); + ASSERT_EQ(material.NumTextureMaps(), 3); + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::ROUGHNESS), + nullptr); + + material.SetTransparencyMode(draco::Material::TRANSPARENCY_BLEND); + ASSERT_EQ(material.GetTransparencyMode(), + draco::Material::TRANSPARENCY_BLEND); + material.SetAlphaCutoff(0.2f); + ASSERT_EQ(material.GetAlphaCutoff(), 0.2f); + material.SetNormalTextureScale(0.75f); + ASSERT_EQ(material.GetNormalTextureScale(), 0.75f); + + material.ClearTextureMaps(); + ASSERT_EQ(material.NumTextureMaps(), 0); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + + // Metallic factor should be unchanged. + ASSERT_EQ(material.GetMetallicFactor(), 0.3f); + + material.Clear(); + // Metallic factor should be reset to default. + ASSERT_NE(material.GetMetallicFactor(), 0.3f); + + ASSERT_EQ(material.GetDoubleSided(), false); + material.SetDoubleSided(true); + ASSERT_EQ(material.GetDoubleSided(), true); +} + +TEST(MaterialTest, TestMaterialCopy) { + draco::Material material; + material.SetName("Antimatter"); + material.SetColorFactor(draco::Vector4f(0.3f, 0.2f, 0.4f, 0.9f)); + material.SetMetallicFactor(0.2f); + material.SetRoughnessFactor(0.4f); + material.SetEmissiveFactor(draco::Vector3f(0.3f, 0.1f, 0.2f)); + material.SetTransparencyMode(draco::Material::TRANSPARENCY_MASK); + material.SetAlphaCutoff(0.25f); + material.SetDoubleSided(true); + material.SetNormalTextureScale(0.75f); + + // Set the properties of material extensions. + material.SetUnlit(true); + material.SetHasSheen(true); + material.SetSheenColorFactor(draco::Vector3f(0.4f, 0.2f, 0.8f)); + material.SetSheenRoughnessFactor(0.428f); + material.SetHasTransmission(true); + material.SetTransmissionFactor(0.5f); + material.SetHasClearcoat(true); + material.SetClearcoatFactor(0.6f); + material.SetClearcoatRoughnessFactor(0.7f); + material.SetHasVolume(true); + material.SetThicknessFactor(0.8f); + material.SetAttenuationDistance(0.9f); + material.SetAttenuationColor(draco::Vector3f(0.2f, 0.5f, 0.8f)); + material.SetHasIor(true); + material.SetIor(1.1f); + material.SetHasSpecular(true); + material.SetSpecularFactor(0.01f); + material.SetSpecularColorFactor(draco::Vector3f(0.4f, 1.f, 1.f)); + + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + material.SetTextureMap(std::move(texture), draco::TextureMap::EMISSIVE, 2); + + draco::Material new_material; + new_material.Copy(material); + + ASSERT_EQ(material.GetName(), new_material.GetName()); + ASSERT_EQ(material.GetColorFactor(), new_material.GetColorFactor()); + ASSERT_EQ(material.GetMetallicFactor(), new_material.GetMetallicFactor()); + ASSERT_EQ(material.GetRoughnessFactor(), new_material.GetRoughnessFactor()); + ASSERT_EQ(material.GetEmissiveFactor(), new_material.GetEmissiveFactor()); + ASSERT_EQ(material.GetTransparencyMode(), new_material.GetTransparencyMode()); + ASSERT_EQ(material.GetAlphaCutoff(), new_material.GetAlphaCutoff()); + ASSERT_EQ(material.GetDoubleSided(), new_material.GetDoubleSided()); + ASSERT_EQ(material.GetNormalTextureScale(), + new_material.GetNormalTextureScale()); + + // Check that the properties of material extensions have been copied. + ASSERT_EQ(material.GetUnlit(), new_material.GetUnlit()); + ASSERT_EQ(material.HasSheen(), new_material.HasSheen()); + ASSERT_EQ(material.GetSheenColorFactor(), new_material.GetSheenColorFactor()); + ASSERT_EQ(material.GetSheenRoughnessFactor(), + new_material.GetSheenRoughnessFactor()); + ASSERT_TRUE(material.HasTransmission()); + ASSERT_EQ(material.GetTransmissionFactor(), + new_material.GetTransmissionFactor()); + ASSERT_TRUE(material.HasClearcoat()); + ASSERT_EQ(material.GetClearcoatFactor(), new_material.GetClearcoatFactor()); + ASSERT_EQ(material.GetClearcoatRoughnessFactor(), + new_material.GetClearcoatRoughnessFactor()); + ASSERT_TRUE(material.HasVolume()); + ASSERT_EQ(material.GetThicknessFactor(), new_material.GetThicknessFactor()); + ASSERT_EQ(material.GetAttenuationDistance(), + new_material.GetAttenuationDistance()); + ASSERT_EQ(material.GetAttenuationColor(), new_material.GetAttenuationColor()); + ASSERT_TRUE(material.HasIor()); + ASSERT_EQ(material.GetIor(), new_material.GetIor()); + ASSERT_TRUE(material.HasSpecular()); + ASSERT_EQ(material.GetSpecularFactor(), new_material.GetSpecularFactor()); + ASSERT_EQ(material.GetSpecularColorFactor(), + new_material.GetSpecularColorFactor()); + + for (int i = 0; i < draco::TextureMap::TEXTURE_TYPES_COUNT; ++i) { + const draco::TextureMap::Type texture_map_type = + static_cast(i); + if (material.GetTextureMapByType(texture_map_type) == nullptr) { + ASSERT_EQ(new_material.GetTextureMapByType(texture_map_type), nullptr); + continue; + } + if (material.GetTextureMapByType(texture_map_type)->texture() == nullptr) { + ASSERT_EQ(new_material.GetTextureMapByType(texture_map_type)->texture(), + nullptr); + } else { + ASSERT_NE(new_material.GetTextureMapByType(texture_map_type)->texture(), + nullptr); + ASSERT_EQ( + material.GetTextureMapByType(texture_map_type)->tex_coord_index(), + new_material.GetTextureMapByType(texture_map_type) + ->tex_coord_index()); + } + } + + ASSERT_EQ(material.NumTextureMaps(), new_material.NumTextureMaps()); + for (int i = 0; i < material.NumTextureMaps(); ++i) { + const draco::TextureMap *const tm0 = material.GetTextureMapByIndex(i); + const draco::TextureMap *const tm1 = new_material.GetTextureMapByIndex(i); + ASSERT_NE(tm0, nullptr); + ASSERT_NE(tm1, nullptr); + ASSERT_EQ(tm0->type(), tm1->type()); + } +} + +TEST(MaterialTest, RemoveTextureMap) { + // Tests that we can remove existing texture maps from a material. + draco::Material material; + + // Add some dummy textures to the material. + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + std::unique_ptr texture_2 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + + material.SetTextureMap(std::move(texture), draco::TextureMap::EMISSIVE, 0); + + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Try to delete the color texture. + std::unique_ptr removed_texture = + material.RemoveTextureMapByType(draco::TextureMap::COLOR); + ASSERT_NE(removed_texture, nullptr); + ASSERT_EQ(removed_texture->type(), draco::TextureMap::COLOR); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); + ASSERT_EQ(material.GetTextureMapByIndex(0)->type(), + draco::TextureMap::EMISSIVE); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + + removed_texture = material.RemoveTextureMapByIndex(0); + ASSERT_NE(removed_texture, nullptr); + ASSERT_EQ(removed_texture->type(), draco::TextureMap::EMISSIVE); + ASSERT_EQ(material.NumTextureMaps(), 0); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); +} + +TEST(MaterialTest, SharedTexture) { + // Tests adding shared textures. + draco::Material material; + + // Add some dummy textures to the material. + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + draco::Texture *texture_raw = texture.get(); + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + DRACO_ASSERT_OK( + material.SetTextureMap(texture_raw, draco::TextureMap::EMISSIVE, 0)); + + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Read a new texture. + texture = draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + // Texture is not owned by the material so we expect a failure. + ASSERT_FALSE( + material + .SetTextureMap(texture.get(), draco::TextureMap::AMBIENT_OCCLUSION, 0) + .ok()); +} + +} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.cc new file mode 100644 index 000000000..7f9fcb621 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.cc @@ -0,0 +1,14 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.h new file mode 100644 index 000000000..7f9fcb621 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils.h @@ -0,0 +1,14 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// diff --git a/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils_test.cc new file mode 100644 index 000000000..82a1227a2 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/material/material_utils_test.cc @@ -0,0 +1,24 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_utils.h" + +#include +#include +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace {} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.cc index 3f92f651a..6494c1572 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.cc @@ -15,6 +15,7 @@ #include "draco/mesh/corner_table.h" #include +#include #include "draco/attributes/geometry_indices.h" #include "draco/mesh/corner_table_iterators.h" diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.h index 3aa720fde..3088931c1 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table.h @@ -21,6 +21,7 @@ #include "draco/attributes/geometry_indices.h" #include "draco/core/draco_index_type_vector.h" #include "draco/core/macros.h" +#include "draco/draco_features.h" #include "draco/mesh/valence_cache.h" namespace draco { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_iterators.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_iterators.h index 7122aa1be..72c70ac32 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_iterators.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_iterators.h @@ -15,15 +15,23 @@ #ifndef DRACO_MESH_CORNER_TABLE_ITERATORS_H_ #define DRACO_MESH_CORNER_TABLE_ITERATORS_H_ +#include + #include "draco/mesh/corner_table.h" namespace draco { // Class for iterating over vertices in a 1-ring around the specified vertex. template -class VertexRingIterator - : public std::iterator { +class VertexRingIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = VertexIndex; + using difference_type = std::ptrdiff_t; + using pointer = VertexIndex *; + using reference = VertexIndex &; + // std::iterator interface requires a default constructor. VertexRingIterator() : corner_table_(nullptr), @@ -111,9 +119,15 @@ class VertexRingIterator // Class for iterating over faces adjacent to the specified input face. template -class FaceAdjacencyIterator - : public std::iterator { +class FaceAdjacencyIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = FaceIndex; + using difference_type = std::ptrdiff_t; + using pointer = FaceIndex *; + using reference = FaceIndex &; + // std::iterator interface requires a default constructor. FaceAdjacencyIterator() : corner_table_(nullptr), @@ -193,9 +207,15 @@ class FaceAdjacencyIterator // Class for iterating over corners attached to a specified vertex. template -class VertexCornersIterator - : public std::iterator { +class VertexCornersIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = CornerIndex; + using difference_type = std::ptrdiff_t; + using pointer = CornerIndex *; + using reference = CornerIndex &; + // std::iterator interface requires a default constructor. VertexCornersIterator() : corner_table_(nullptr), diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_test.cc new file mode 100644 index 000000000..f88d3ec96 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/corner_table_test.cc @@ -0,0 +1,126 @@ +#include "draco/mesh/corner_table.h" + +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/obj_decoder.h" +#include "draco/mesh/mesh_connected_components.h" +#include "draco/mesh/mesh_misc_functions.h" + +namespace draco { + +class CornerTableTest : public ::testing::Test { + protected: + std::unique_ptr DecodeObj(const std::string &file_name) const { + const std::string path = GetTestFileFullPath(file_name); + ObjDecoder decoder; + std::unique_ptr mesh(new Mesh()); + if (!decoder.DecodeFromFile(path, mesh.get()).ok()) { + return nullptr; + } + return mesh; + } + + void TestEncodingCube() { + // Build a CornerTable looking at the mesh and verify that the caching of + // valences are reasonably correct and within range of expectations. This + // test is built specifically for working with 'cubes' and has expectations + // about the degree of each corner. + const std::string file_name = "cube_att.obj"; + std::unique_ptr in_mesh = DecodeObj(file_name); + ASSERT_NE(in_mesh, nullptr) << "Failed to load test model " << file_name; + draco::Mesh *mesh = nullptr; + mesh = in_mesh.get(); + + std::unique_ptr utable = + draco::CreateCornerTableFromPositionAttribute(mesh); + draco::CornerTable *table = utable.get(); + + table->GetValenceCache().CacheValences(); + table->GetValenceCache().CacheValencesInaccurate(); + + for (VertexIndex index = static_cast(0); + index < static_cast(table->num_vertices()); index++) { + const auto valence = table->Valence(index); + const auto valence2 = table->GetValenceCache().ValenceFromCache(index); + const auto valence3 = + table->GetValenceCache().ValenceFromCacheInaccurate(index); + ASSERT_EQ(valence, valence2); + ASSERT_GE(valence, valence3); // may be clipped. + + // No more than 6 triangles can touch a cube corner. + ASSERT_LE(valence, 6); + ASSERT_LE(valence2, 6); + ASSERT_LE(valence3, 6); + + // No less than 3 triangles can touch a cube corner. + ASSERT_GE(valence, 3); + ASSERT_GE(valence2, 3); + ASSERT_GE(valence3, 3); + } + + for (CornerIndex index = static_cast(0); + index < static_cast(table->num_corners()); index++) { + const auto valence = table->Valence(index); + const auto valence2 = table->GetValenceCache().ValenceFromCache(index); + const auto valence3 = + table->GetValenceCache().ValenceFromCacheInaccurate(index); + ASSERT_EQ(valence, valence2); + ASSERT_GE(valence, valence3); // may be clipped. + + // No more than 6 triangles can touch a cube corner, 6 edges result. + ASSERT_LE(valence, 6); + ASSERT_LE(valence2, 6); + ASSERT_LE(valence3, 6); + + // No less than 3 triangles can touch a cube corner, 3 edges result. + ASSERT_GE(valence, 3); + ASSERT_GE(valence2, 3); + ASSERT_GE(valence3, 3); + } + + table->GetValenceCache().ClearValenceCache(); + table->GetValenceCache().ClearValenceCacheInaccurate(); + } +}; + +TEST_F(CornerTableTest, NormalWithSeams) { TestEncodingCube(); } + +TEST_F(CornerTableTest, TestNonManifoldEdges) { + std::unique_ptr mesh = DecodeObj("non_manifold_wrap.obj"); + ASSERT_NE(mesh, nullptr); + std::unique_ptr ct = + draco::CreateCornerTableFromPositionAttribute(mesh.get()); + ASSERT_NE(ct, nullptr); + + MeshConnectedComponents connected_components; + connected_components.FindConnectedComponents(ct.get()); + ASSERT_EQ(connected_components.NumConnectedComponents(), 2); +} + +TEST_F(CornerTableTest, TestNewFace) { + // Tests that we can add a new face to the corner table. + const std::string file_name = "cube_att.obj"; + std::unique_ptr mesh = DecodeObj(file_name); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr ct = + draco::CreateCornerTableFromPositionAttribute(mesh.get()); + ASSERT_NE(ct, nullptr); + ASSERT_EQ(ct->num_faces(), 12); + ASSERT_EQ(ct->num_corners(), 3 * 12); + ASSERT_EQ(ct->num_vertices(), 8); + + const VertexIndex new_vi = ct->AddNewVertex(); + ASSERT_EQ(ct->num_vertices(), 9); + + ASSERT_EQ(ct->AddNewFace({VertexIndex(6), VertexIndex(7), new_vi}), 12); + ASSERT_EQ(ct->num_faces(), 13); + ASSERT_EQ(ct->num_corners(), 3 * 13); + + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12 + 0)), 6); + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12) + 1), 7); + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12) + 2), new_vi); +} + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.cc index 3be4b1494..b287ecb45 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.cc @@ -15,6 +15,10 @@ #include "draco/mesh/mesh.h" #include +#include +#include +#include +#include namespace draco { @@ -22,7 +26,436 @@ namespace draco { template using conditional_t = typename std::conditional::type; +#ifdef DRACO_TRANSCODER_SUPPORTED +Mesh::Mesh() : compression_enabled_(false) {} +#else Mesh::Mesh() {} +#endif + +#ifdef DRACO_TRANSCODER_SUPPORTED +void Mesh::Copy(const Mesh &src) { + PointCloud::Copy(src); + name_ = src.name_; + faces_ = src.faces_; + attribute_data_ = src.attribute_data_; + material_library_.Copy(src.material_library_); + compression_enabled_ = src.compression_enabled_; + compression_options_ = src.compression_options_; + + // Copy mesh feature ID sets. + mesh_features_.clear(); + for (MeshFeaturesIndex i(0); i < src.NumMeshFeatures(); i++) { + std::unique_ptr mesh_features(new MeshFeatures()); + mesh_features->Copy(src.GetMeshFeatures(i)); + AddMeshFeatures(std::move(mesh_features)); + } + mesh_features_material_mask_ = src.mesh_features_material_mask_; + + // Copy non-material textures. + non_material_texture_library_.Copy(src.non_material_texture_library_); + + // Update pointers to non-material textures in mesh feature ID sets. + if (non_material_texture_library_.NumTextures() != 0) { + const auto texture_to_index_map = + src.non_material_texture_library_.ComputeTextureToIndexMap(); + for (MeshFeaturesIndex j(0); j < NumMeshFeatures(); ++j) { + Mesh::UpdateMeshFeaturesTexturePointer(texture_to_index_map, + &non_material_texture_library_, + &GetMeshFeatures(j)); + } + } + + // Copy structural metadata. + structural_metadata_.Copy(src.structural_metadata_); +} + +namespace { +// A helper struct that augments a point index with an attribute value index. +// A unique combination of |point_index| and |attribute_value_index| +// corresponds to a unique point on the mesh. Used to identify unique points +// after a new attribute is added to the mesh. +struct AugmentedPointData { + PointIndex point_index; + AttributeValueIndex attribute_value_index; + bool operator<(const AugmentedPointData &pd) const { + if (point_index < pd.point_index) { + return true; + } + if (point_index > pd.point_index) { + return false; + } + return attribute_value_index < pd.attribute_value_index; + } +}; +} // namespace + +int32_t Mesh::AddAttributeWithConnectivity( + std::unique_ptr att, + const IndexTypeVector &corner_to_value) { + // Map between augmented point and new point indices (one augmented point + // corresponds to one PointIndex). + std::map old_to_new_point_map; + + // Map between corners and the new point indices. + IndexTypeVector corner_to_point(num_faces() * 3, + kInvalidPointIndex); + + // Flag whether a given existing point index has been used. Used to ensure + // that mapping between existing and new point indices that are smaller + // than num_points() is identity. In other words, we want to keep indices of + // the existing points intact and add new points to end. + IndexTypeVector is_point_used(num_points(), false); + + int new_num_points = num_points(); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + AugmentedPointData apd; + apd.point_index = CornerToPointId(ci); + apd.attribute_value_index = corner_to_value[ci]; + const auto it = old_to_new_point_map.find(apd); + if (it != old_to_new_point_map.end()) { + // Augmented point is already mapped to a point index. Reuse it. + corner_to_point[ci] = it->second; + } else { + // New combination of point index + attribute value index. Map it to a + // unique point index. + PointIndex new_point_index; + if (!is_point_used[apd.point_index]) { + // Reuse the existing (old) point index. + new_point_index = apd.point_index; + is_point_used[apd.point_index] = true; + } else { + // Add a new point index to the end. + new_point_index = PointIndex(new_num_points++); + } + old_to_new_point_map[apd] = new_point_index; + corner_to_point[ci] = new_point_index; + } + } + + // Update point to attribute value mapping for the new attribute. + att->SetExplicitMapping(new_num_points); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + att->SetPointMapEntry(corner_to_point[ci], corner_to_value[ci]); + } + + // Update point to attribute value mapping on the remaining attributes if + // needed. + if (new_num_points > num_points()) { + set_num_points(new_num_points); + + // Setup attributes for the new number of points. + for (int ai = 0; ai < num_attributes(); ++ai) { + const bool mapping_was_identity = attribute(ai)->is_mapping_identity(); + attribute(ai)->SetExplicitMapping(new_num_points); + if (mapping_was_identity) { + // Convert all old points from identity to explicit mapping. + for (AttributeValueIndex avi(0); avi < attribute(ai)->size(); ++avi) { + attribute(ai)->SetPointMapEntry(PointIndex(avi.value()), avi); + } + } + } + + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + const PointIndex old_point_index = CornerToPointId(ci); + const PointIndex new_point_index = corner_to_point[ci]; + if (old_point_index == new_point_index) { + continue; + } + // Update point to value mapping for all existing attributes. + for (int ai = 0; ai < num_attributes(); ++ai) { + attribute(ai)->SetPointMapEntry( + new_point_index, attribute(ai)->mapped_index(old_point_index)); + } + // Update mapping between the corner and the new point index. + faces_[FaceIndex(ci.value() / 3)][ci.value() % 3] = new_point_index; + } + } + + // If any of the old points have not been used, initialize dummy mapping for + // the new attribute. + for (PointIndex pi(0); pi < is_point_used.size(); ++pi) { + if (!is_point_used[pi]) { + att->SetPointMapEntry(pi, AttributeValueIndex(0)); + } + } + + return PointCloud::AddAttribute(std::move(att)); +} + +int32_t Mesh::AddPerVertexAttribute(std::unique_ptr att) { + const PointAttribute *const pos_att = + GetNamedAttribute(GeometryAttribute::POSITION); + if (pos_att == nullptr) { + return -1; + } + if (att->size() != pos_att->size()) { + return -1; // Number of values must be same as in the position attribute. + } + + if (pos_att->is_mapping_identity()) { + att->SetIdentityMapping(); + } else { + // Copy point to attribute value mapping from the position attribute to + // |att|. + att->SetExplicitMapping(num_points()); + for (PointIndex pi(0); pi < num_points(); ++pi) { + att->SetPointMapEntry(pi, pos_att->mapped_index(pi)); + } + } + + return PointCloud::AddAttribute(std::move(att)); +} + +void Mesh::RemoveIsolatedPoints() { + // For each point, check if it is mapped to a face. + IndexTypeVector is_point_used(num_points(), false); + int num_used_points = 0; + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + const auto &f = face(fi); + for (int c = 0; c < 3; ++c) { + if (!is_point_used[f[c]]) { + num_used_points++; + is_point_used[f[c]] = true; + } + } + } + if (num_used_points == num_points()) { + return; // All points are used. + } + + // Create mapping between the old and new point indices. + IndexTypeVector old_to_new_point_map( + num_points(), kInvalidPointIndex); + PointIndex new_point_index(0); + for (PointIndex pi(0); pi < num_points(); ++pi) { + if (is_point_used[pi]) { + old_to_new_point_map[pi] = new_point_index++; + } + } + + // Update point to attribute value index map for all attributes. + for (int ai = 0; ai < num_attributes(); ++ai) { + PointAttribute *att = attribute(ai); + if (att->is_mapping_identity()) { + // When the attribute uses identity mapping we need to reorder to the + // attribute values to match the new point indices. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const PointIndex new_pi = old_to_new_point_map[pi]; + if (new_pi == pi || new_pi == kInvalidPointIndex) { + continue; + } + att->SetAttributeValue( + AttributeValueIndex(new_pi.value()), + att->GetAddress(AttributeValueIndex(pi.value()))); + } + att->Resize(num_used_points); + } else { + // For explicitly mapped attributes, we first update the point to + // attribute value mapping and then we remove all unused values from the + // attribute. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const PointIndex new_pi = old_to_new_point_map[pi]; + if (new_pi == pi || new_pi == kInvalidPointIndex) { + continue; + } + att->SetPointMapEntry(new_pi, att->mapped_index(pi)); + } + att->SetExplicitMapping(num_used_points); + + att->RemoveUnusedValues(); + } + } + + // Update the mapping between faces and point indices. + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + auto &f = faces_[fi]; + for (int c = 0; c < 3; ++c) { + f[c] = old_to_new_point_map[f[c]]; + } + } + + set_num_points(num_used_points); +} + +void Mesh::RemoveUnusedMaterials() { RemoveUnusedMaterials(true); } + +void Mesh::RemoveUnusedMaterials(bool remove_unused_material_indices) { + const int mat_att_index = GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (mat_att_index == -1) { + // Remove all materials except for the first one. + while (GetMaterialLibrary().NumMaterials() > 1) { + GetMaterialLibrary().RemoveMaterial(1); + } + GetMaterialLibrary().RemoveUnusedTextures(); + return; + } + auto mat_att = attribute(mat_att_index); + + // Deduplicate attribute values in the material attribute to ensure that one + // attribute value index corresponds to one unique material index. + // Note that this does not remove unused material indices. + mat_att->DeduplicateValues(*mat_att); + + // Gather all material indices that are referenced by faces of the mesh. + const int num_materials = GetMaterialLibrary().NumMaterials(); + std::vector is_material_used(num_materials, false); + int num_used_materials = 0; + + // Helper function that updates |is_material_used| for the processed mesh. + auto update_used_materials = [&is_material_used, &num_used_materials, mat_att, + num_materials](PointIndex pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + if (mat_index < num_materials) { + if (!is_material_used[mat_index]) { + is_material_used[mat_index] = true; + num_used_materials++; + } + } + }; + + if (num_faces() > 0) { + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + update_used_materials(faces_[fi][0]); + } + } else { + // Handle the mesh as a point cloud and check materials used by points. + for (PointIndex pi(0); pi < num_points(); ++pi) { + update_used_materials(pi); + } + } + + // Check if any of the (unused) materials is used by mesh features. If so, + // user should remove unused mesh features first. + for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) { + for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + const int mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index); + if (mat_index < num_materials && !is_material_used[mat_index]) { + is_material_used[mat_index] = true; + num_used_materials++; + } + } + } + + if (num_used_materials == num_materials) { + return; // All materials are used, don't do anything. + } + + // Remove unused materials from the material library or replace them with + // default materials if we do not remove unused material indices. + for (int mi = num_materials - 1; mi >= 0; --mi) { + if (!is_material_used[mi] && mi < GetMaterialLibrary().NumMaterials()) { + if (remove_unused_material_indices) { + GetMaterialLibrary().RemoveMaterial(mi); + } else { + GetMaterialLibrary().MutableMaterial(mi)->Clear(); + } + } + } + GetMaterialLibrary().RemoveUnusedTextures(); + + if (!remove_unused_material_indices) { + // All the code below handles updating of material indices. Since we do not + // want to update them, we can return early. + return; + } + + // Compute map between old and new material indices. + std::vector old_to_new_material_index_map(num_materials, -1); + for (int mi = 0, new_material_index = 0; mi < num_materials; ++mi) { + if (is_material_used[mi]) { + old_to_new_material_index_map[mi] = new_material_index; + ++new_material_index; + } + } + IndexTypeVector + old_to_new_material_attribute_value_index_map(mat_att->size(), -1); + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + if (mat_index < num_materials && is_material_used[mat_index]) { + old_to_new_material_attribute_value_index_map[avi] = + old_to_new_material_index_map[mat_index]; + } + } + + // Update attribute values with the new number of materials. + mat_att->Reset(num_used_materials); + + // Set identity mapping between AttributeValueIndex and material indices. + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + const uint32_t mat_index = avi.value(); + mat_att->SetAttributeValue(avi, &mat_index); + } + + // Update mapping between points and attribute values. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const AttributeValueIndex old_avi = mat_att->mapped_index(pi); + mat_att->SetPointMapEntry( + pi, AttributeValueIndex( + old_to_new_material_attribute_value_index_map[old_avi])); + } + + // Update material indices on mesh features. + for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) { + for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + const int old_mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index); + if (old_mat_index < num_materials && is_material_used[old_mat_index]) { + mesh_features_material_mask_[mfi][mask_index] = + old_to_new_material_index_map[old_mat_index]; + } + } + } +} + +void Mesh::UpdateMeshFeaturesTexturePointer( + const std::unordered_map &texture_to_index_map, + TextureLibrary *texture_library, MeshFeatures *mesh_features) { + TextureMap &texture_map = mesh_features->GetTextureMap(); + if (texture_map.texture() == nullptr) { + return; + } + const auto it = texture_to_index_map.find(texture_map.texture()); + DRACO_DCHECK(it != texture_to_index_map.end()); + const int texture_index = it->second; + DRACO_DCHECK(texture_index < texture_library->NumTextures()); + texture_map.SetTexture(texture_library->GetTexture(texture_index)); +} + +void Mesh::CopyMeshFeaturesForMaterial(const Mesh &source_mesh, + Mesh *target_mesh, int material_index) { + for (MeshFeaturesIndex mfi(0); mfi < source_mesh.NumMeshFeatures(); ++mfi) { + // Mesh features is used if it doesn't have any material mask or if one + // of the material masks matches |material_index|. + bool is_used = source_mesh.NumMeshFeaturesMaterialMasks(mfi) == 0; + for (int mask_index = 0; + !is_used && mask_index < source_mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + if (source_mesh.GetMeshFeaturesMaterialMask(mfi, mask_index) == + material_index) { + is_used = true; + } + } + if (is_used) { + // Copy over the mesh features to the target mesh. Note that texture + // pointers are not updated at this step. + std::unique_ptr new_mf(new MeshFeatures()); + new_mf->Copy(source_mesh.GetMeshFeatures(mfi)); + target_mesh->AddMeshFeatures(std::move(new_mf)); + } + } +} + +int32_t Mesh::AddPerFaceAttribute(std::unique_ptr att) { + IndexTypeVector corner_map(num_faces() * 3); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + corner_map[ci] = AttributeValueIndex(ci.value() / 3); + } + return AddAttributeWithConnectivity(std::move(att), corner_map); +} +#endif // DRACO_TRANSCODER_SUPPORTED #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED void Mesh::ApplyPointIdDeduplication( diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.h index f4506da81..652c2c010 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh.h @@ -16,12 +16,20 @@ #define DRACO_MESH_MESH_H_ #include +#include #include "draco/attributes/geometry_indices.h" #include "draco/core/hash_utils.h" #include "draco/core/macros.h" #include "draco/core/status.h" #include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/material/material_library.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_indices.h" +#include "draco/metadata/structural_metadata.h" +#endif #include "draco/point_cloud/point_cloud.h" namespace draco { @@ -47,6 +55,11 @@ class Mesh : public PointCloud { Mesh(); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies all data from the |src| mesh. + void Copy(const Mesh &src); +#endif + void AddFace(const Face &face) { faces_.push_back(face); } void SetFace(FaceIndex face_id, const Face &face) { @@ -83,6 +96,38 @@ class Mesh : public PointCloud { } } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. Attribute connectivity data is specified in + // |corner_to_value| array that contains mapping between face corners and + // attribute value indices. + // The purpose of this function is to allow users to add attributes with + // arbitrary connectivity to an existing mesh. New points will be + // automatically created if needed. + int32_t AddAttributeWithConnectivity( + std::unique_ptr att, + const IndexTypeVector &corner_to_value); + + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. The inserted attribute must have the same + // connectivity as the position attribute of the mesh (that is, the attribute + // values are defined per-vertex). Each attribute value entry in |att| + // corresponds to the corresponding attribute value entry in the position + // attribute (AttributeValueIndex in both attributes refer to the same + // spatial vertex). + // Returns -1 in case of error. + int32_t AddPerVertexAttribute(std::unique_ptr att); + + // Removes points that are not mapped to any face of the mesh. All attribute + // values are going to be removed as well. + void RemoveIsolatedPoints(); + + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. Attribute values are mapped 1:1 to face indices. + // Returns -1 in case of error. + int32_t AddPerFaceAttribute(std::unique_ptr att); +#endif // DRACO_TRANSCODER_SUPPORTED + MeshAttributeElementType GetAttributeElementType(int att_id) const { return attribute_data_[att_id].element_type; } @@ -109,6 +154,103 @@ class Mesh : public PointCloud { MeshAttributeElementType element_type; }; +#ifdef DRACO_TRANSCODER_SUPPORTED + void SetName(const std::string &name) { name_ = name; } + const std::string &GetName() const { return name_; } + const MaterialLibrary &GetMaterialLibrary() const { + return material_library_; + } + MaterialLibrary &GetMaterialLibrary() { return material_library_; } + + // Removes all materials that are not referenced by any face of the mesh. + // Optional argument |remove_unused_material_indices| can be used to control + // whether unusued material indices are removed as well (default = true). + // If material indices are not removed, the unused material indices will + // point to empty (default) materials. + void RemoveUnusedMaterials(); + void RemoveUnusedMaterials(bool remove_unused_material_indices); + + // Enables or disables Draco geometry compression for this mesh. + void SetCompressionEnabled(bool enabled) { compression_enabled_ = enabled; } + bool IsCompressionEnabled() const { return compression_enabled_; } + + // Sets |options| that configure Draco geometry compression. This does not + // enable or disable compression. + void SetCompressionOptions(const DracoCompressionOptions &options) { + compression_options_ = options; + } + const DracoCompressionOptions &GetCompressionOptions() const { + return compression_options_; + } + DracoCompressionOptions &GetCompressionOptions() { + return compression_options_; + } + + // Library that contains non-material textures. + const TextureLibrary &GetNonMaterialTextureLibrary() const { + return non_material_texture_library_; + } + TextureLibrary &GetNonMaterialTextureLibrary() { + return non_material_texture_library_; + } + + // Mesh feature ID sets as defined by EXT_mesh_features glTF extension. + MeshFeaturesIndex AddMeshFeatures( + std::unique_ptr mesh_features) { + mesh_features_.push_back(std::move(mesh_features)); + mesh_features_material_mask_.push_back({}); + return MeshFeaturesIndex(mesh_features_.size() - 1); + } + int NumMeshFeatures() const { return mesh_features_.size(); } + const MeshFeatures &GetMeshFeatures(MeshFeaturesIndex index) const { + return *mesh_features_[index]; + } + MeshFeatures &GetMeshFeatures(MeshFeaturesIndex index) { + return *mesh_features_[index]; + } + void RemoveMeshFeatures(MeshFeaturesIndex index) { + mesh_features_.erase(mesh_features_.begin() + index.value()); + mesh_features_material_mask_.erase(mesh_features_material_mask_.begin() + + index.value()); + } + + // Restricts given mesh features to faces mapped to a material with + // |material_index|. Note that single mesh features can be restricted to + // multiple materials. + void AddMeshFeaturesMaterialMask(MeshFeaturesIndex index, + int material_index) { + mesh_features_material_mask_[index].push_back(material_index); + } + + size_t NumMeshFeaturesMaterialMasks(MeshFeaturesIndex index) const { + return mesh_features_material_mask_[index].size(); + } + int GetMeshFeaturesMaterialMask(MeshFeaturesIndex index, + int mask_index) const { + return mesh_features_material_mask_[index][mask_index]; + } + + // Updates mesh features texture pointer to point to a new |texture_library|. + // The current texture pointer is used to determine the texture index in the + // new texture library via a given |texture_to_index_map|. + static void UpdateMeshFeaturesTexturePointer( + const std::unordered_map &texture_to_index_map, + TextureLibrary *texture_library, MeshFeatures *mesh_features); + + // Copies over mesh features from |source_mesh| and stores them in + // |target_mesh| as long as the mesh features material mask is valid for + // given |material_index|. + static void CopyMeshFeaturesForMaterial(const Mesh &source_mesh, + Mesh *target_mesh, + int material_index); + + // Structural metadata. + const StructuralMetadata &GetStructuralMetadata() const { + return structural_metadata_; + } + StructuralMetadata &GetStructuralMetadata() { return structural_metadata_; } +#endif // DRACO_TRANSCODER_SUPPORTED + protected: #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED // Extends the point deduplication to face corners. This method is called from @@ -119,6 +261,10 @@ class Mesh : public PointCloud { const std::vector &unique_point_ids) override; #endif + // Exposes |faces_|. Use |faces_| at your own risk. DO NOT store the + // reference: the |faces_| object is destroyed with the mesh. + IndexTypeVector &faces() { return faces_; } + private: // Mesh specific per-attribute data. std::vector attribute_data_; @@ -127,6 +273,40 @@ class Mesh : public PointCloud { // that converts vertex indices into attribute indices. IndexTypeVector faces_; +#ifdef DRACO_TRANSCODER_SUPPORTED + // Mesh name. + std::string name_; + + // Materials applied to to this mesh. + MaterialLibrary material_library_; + + // Compression options for this mesh. + // TODO(vytyaz): Store encoded bitstream that this mesh compresses into. + bool compression_enabled_; + DracoCompressionOptions compression_options_; + + // Sets of feature IDs as defined by EXT_mesh_features glTF extension. + IndexTypeVector> + mesh_features_; + + // When the Mesh contains multiple materials, |mesh_features_material_mask_| + // can be used to limit specific MeshFeaturesIndex to a vector of material + // indices. If for a given mesh feature index, the material indices are empty, + // the corresponding mesh features are applied to the entire mesh. + IndexTypeVector> + mesh_features_material_mask_; + + // Texture library for storing non-material textures used by this mesh, e.g., + // textures containing mesh feature IDs of EXT_mesh_features glTF extension. + // If the mesh is part of the scene then the textures are stored in the scene. + // Note that mesh features contain pointers to non-material textures. It is + // responsibility of class user to update these pointers when updating the + // textures. See Mesh::Copy() for example. + TextureLibrary non_material_texture_library_; + + // Structural metadata defined by the EXT_structural_metadata glTF extension. + StructuralMetadata structural_metadata_; +#endif // DRACO_TRANSCODER_SUPPORTED friend struct MeshHasher; }; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc index b832379af..305811f10 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc @@ -15,6 +15,9 @@ #include "draco/mesh/mesh_are_equivalent.h" #include +#include + +#include "draco/texture/texture_utils.h" namespace draco { @@ -114,6 +117,55 @@ bool MeshAreEquivalent::operator()(const Mesh &mesh0, const Mesh &mesh1) { // face with respect to lex order. Init(mesh0, mesh1); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Compare geometry compression settings. + if (mesh0.IsCompressionEnabled() != mesh1.IsCompressionEnabled()) { + return false; + } + if (mesh0.GetCompressionOptions() != mesh1.GetCompressionOptions()) { + return false; + } + + // Compare non-material texture library sizes. + if (mesh0.GetNonMaterialTextureLibrary().NumTextures() != + mesh1.GetNonMaterialTextureLibrary().NumTextures()) { + return false; + } + + // Compare mesh feature ID sets. + if (mesh0.NumMeshFeatures() != mesh1.NumMeshFeatures()) { + return false; + } + for (MeshFeaturesIndex i(0); i < mesh0.NumMeshFeatures(); ++i) { + const MeshFeatures &features0 = mesh0.GetMeshFeatures(i); + const MeshFeatures &features1 = mesh1.GetMeshFeatures(i); + if (features0.GetAttributeIndex() != features1.GetAttributeIndex()) { + return false; + } + if (features0.GetFeatureCount() != features1.GetFeatureCount()) { + return false; + } + if (features0.GetLabel() != features1.GetLabel()) { + return false; + } + if (features0.GetNullFeatureId() != features1.GetNullFeatureId()) { + return false; + } + if (features0.GetTextureChannels() != features1.GetTextureChannels()) { + return false; + } + if (features0.GetPropertyTableIndex() != + features1.GetPropertyTableIndex()) { + return false; + } + const TextureMap &map0 = features0.GetTextureMap(); + const TextureMap &map1 = features1.GetTextureMap(); + if (map0.tex_coord_index() != map1.tex_coord_index()) { + return false; + } + } +#endif // DRACO_TRANSCODER_SUPPORTED + // Check for every attribute that is valid that every corner is identical. typedef GeometryAttribute::Type AttributeType; const int att_max = AttributeType::NAMED_ATTRIBUTES_COUNT; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc index 74db3f7de..94d8c9c16 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc @@ -15,6 +15,7 @@ #include "draco/mesh/mesh_are_equivalent.h" #include +#include #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" @@ -30,6 +31,14 @@ TEST_F(MeshAreEquivalentTest, TestOnIndenticalMesh) { const std::string file_name = "test_nm.obj"; const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); ASSERT_NE(mesh, nullptr) << "Failed to load test model." << file_name; + +#ifdef DRACO_TRANSCODER_SUPPORTED + // Add mesh feature ID set to the mesh. + std::unique_ptr mesh_features(new MeshFeatures()); + mesh->AddMeshFeatures(std::move(mesh_features)); +#endif + + // Check that mesh is equivalent to itself. MeshAreEquivalent equiv; ASSERT_TRUE(equiv(*mesh, *mesh)); } @@ -95,4 +104,32 @@ TEST_F(MeshAreEquivalentTest, TestOnBigMesh) { ASSERT_TRUE(equiv(*mesh0, *mesh1)); } +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST_F(MeshAreEquivalentTest, TestMeshFeatures) { + const std::string file_name = "test_nm.obj"; + const std::unique_ptr mesh0(ReadMeshFromTestFile(file_name)); + const std::unique_ptr mesh1(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh0, nullptr); + ASSERT_NE(mesh1, nullptr); + + // Add identical mesh feature ID sets to meshes. + mesh0->AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + mesh1->AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + + // Empty feature sets should match. + MeshAreEquivalent equiv; + ASSERT_TRUE(equiv(*mesh0, *mesh1)); + + // Make mesh features different and check that the meshes are not equivalent. + mesh0->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(5); + mesh1->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(6); + ASSERT_FALSE(equiv(*mesh0, *mesh1)); + + // Make mesh features identical and check that the meshes are equivalent. + mesh0->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + mesh1->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + ASSERT_TRUE(equiv(*mesh0, *mesh1)); +} +#endif // DRACO_TRANSCODER_SUPPORTED } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc index 28b68d5fd..54801ce5c 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc @@ -126,18 +126,18 @@ void MeshAttributeCornerTable::AddSeamEdge(CornerIndex c) { } } -void MeshAttributeCornerTable::RecomputeVertices(const Mesh *mesh, +bool MeshAttributeCornerTable::RecomputeVertices(const Mesh *mesh, const PointAttribute *att) { DRACO_DCHECK(GetValenceCache().IsCacheEmpty()); if (mesh != nullptr && att != nullptr) { - RecomputeVerticesInternal(mesh, att); + return RecomputeVerticesInternal(mesh, att); } else { - RecomputeVerticesInternal(nullptr, nullptr); + return RecomputeVerticesInternal(nullptr, nullptr); } } template -void MeshAttributeCornerTable::RecomputeVerticesInternal( +bool MeshAttributeCornerTable::RecomputeVerticesInternal( const Mesh *mesh, const PointAttribute *att) { DRACO_DCHECK(GetValenceCache().IsCacheEmpty()); vertex_to_attribute_entry_id_map_.clear(); @@ -167,6 +167,11 @@ void MeshAttributeCornerTable::RecomputeVerticesInternal( while (act_c != kInvalidCornerIndex) { first_c = act_c; act_c = SwingLeft(act_c); + if (act_c == c) { + // We reached the initial corner which shouldn't happen when we swing + // left from |c|. + return false; + } } } corner_to_vertex_map_[first_c.value()] = VertexIndex(first_vert_id.value()); @@ -189,6 +194,7 @@ void MeshAttributeCornerTable::RecomputeVerticesInternal( act_c = corner_table_->SwingRight(act_c); } } + return true; } int MeshAttributeCornerTable::Valence(VertexIndex v) const { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h index 7dad25cf1..c60be7c86 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h @@ -40,7 +40,7 @@ class MeshAttributeCornerTable { // whenever the seam edges are updated). // |mesh| and |att| can be null, in which case mapping between vertices and // attribute value ids is set to identity. - void RecomputeVertices(const Mesh *mesh, const PointAttribute *att); + bool RecomputeVertices(const Mesh *mesh, const PointAttribute *att); inline bool IsCornerOppositeToSeamEdge(CornerIndex corner) const { return is_edge_on_seam_[corner.value()]; @@ -130,6 +130,12 @@ class MeshAttributeCornerTable { return false; } + bool IsDegenerated(FaceIndex face) const { + // Introducing seams can't change the degeneracy of the individual faces, + // therefore we can delegate the check to the original |corner_table_|. + return corner_table_->IsDegenerated(face); + } + bool no_interior_seams() const { return no_interior_seams_; } const CornerTable *corner_table() const { return corner_table_; } @@ -166,7 +172,7 @@ class MeshAttributeCornerTable { private: template - void RecomputeVerticesInternal(const Mesh *mesh, const PointAttribute *att); + bool RecomputeVerticesInternal(const Mesh *mesh, const PointAttribute *att); std::vector is_edge_on_seam_; std::vector is_vertex_on_seam_; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.cc index 75b55f045..a6dc1823e 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.cc @@ -14,21 +14,25 @@ // #include "draco/mesh/mesh_cleanup.h" +#include +#include #include +#include +#include #include "draco/core/hash_utils.h" namespace draco { -bool MeshCleanup::operator()(Mesh *mesh, const MeshCleanupOptions &options) { +Status MeshCleanup::Cleanup(Mesh *mesh, const MeshCleanupOptions &options) { if (!options.remove_degenerated_faces && !options.remove_unused_attributes && !options.remove_duplicate_faces && !options.make_geometry_manifold) { - return true; // Nothing to cleanup. + return OkStatus(); // Nothing to cleanup. } const PointAttribute *const pos_att = mesh->GetNamedAttribute(GeometryAttribute::POSITION); if (pos_att == nullptr) { - return false; + return Status(Status::DRACO_ERROR, "Missing position attribute."); } if (options.remove_degenerated_faces) { @@ -43,7 +47,7 @@ bool MeshCleanup::operator()(Mesh *mesh, const MeshCleanupOptions &options) { RemoveUnusedAttributes(mesh); } - return true; + return OkStatus(); } void MeshCleanup::RemoveDegeneratedFaces(Mesh *mesh) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.h index 09aae2e1c..c6bdfc6c0 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup.h @@ -16,42 +16,38 @@ #define DRACO_MESH_MESH_CLEANUP_H_ #include "draco/core/status.h" +#include "draco/draco_features.h" #include "draco/mesh/mesh.h" namespace draco { // Options used by the MeshCleanup class. struct MeshCleanupOptions { - MeshCleanupOptions() - : remove_degenerated_faces(true), - remove_duplicate_faces(true), - remove_unused_attributes(true), - make_geometry_manifold(false) {} // If true, the cleanup tool removes any face where two or more vertices // share the same position index. - bool remove_degenerated_faces; + bool remove_degenerated_faces = true; // If true, the cleanup tool removes all duplicate faces. A pair of faces is // duplicate if both faces share the same position indices on all vertices // (that is, position values have to be duduplicated). Note that all // non-position properties are currently ignored. - bool remove_duplicate_faces; + bool remove_duplicate_faces = true; // If true, the cleanup tool removes any unused attribute value or unused // point id. For example, it can be used to remove isolated vertices. - bool remove_unused_attributes; + bool remove_unused_attributes = true; // If true, the cleanup tool splits vertices along non-manifold edges and // vertices. This ensures that the connectivity defined by position indices // is manifold. - bool make_geometry_manifold; + bool make_geometry_manifold = false; }; // Tool that can be used for removing bad or unused data from draco::Meshes. class MeshCleanup { public: // Performs in-place cleanup of the input mesh according to the input options. - bool operator()(Mesh *mesh, const MeshCleanupOptions &options); + static Status Cleanup(Mesh *mesh, const MeshCleanupOptions &options); private: static void RemoveDegeneratedFaces(Mesh *mesh); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc index 89c350e94..76e5206ae 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc @@ -15,6 +15,7 @@ #include "draco/mesh/mesh_cleanup.h" #include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" #include "draco/mesh/triangle_soup_mesh_builder.h" @@ -43,9 +44,7 @@ TEST_F(MeshCleanupTest, TestDegneratedFaces) { ASSERT_NE(mesh, nullptr) << "Failed to build the test mesh."; ASSERT_EQ(mesh->num_faces(), 2) << "Wrong number of faces in the input mesh."; MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; } @@ -89,9 +88,7 @@ TEST_F(MeshCleanupTest, TestDegneratedFacesAndIsolatedVertices) { << "Wrong number of point ids in the input mesh."; ASSERT_EQ(mesh->attribute(int_att_id)->size(), 3); const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; ASSERT_EQ(mesh->num_points(), 3) << "Failed to remove isolated attribute indices."; @@ -133,9 +130,7 @@ TEST_F(MeshCleanupTest, TestAttributes) { ASSERT_EQ(mesh->attribute(1)->size(), 2u) << "Wrong number of generic attribute entries."; const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; ASSERT_EQ(mesh->num_points(), 3) << "Failed to remove isolated attribute indices."; @@ -184,8 +179,7 @@ TEST_F(MeshCleanupTest, TestDuplicateFaces) { ASSERT_NE(mesh, nullptr); ASSERT_EQ(mesh->num_faces(), 5); const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)); + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 2); } diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_connected_components.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_connected_components.h new file mode 100644 index 000000000..6ee30551e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_connected_components.h @@ -0,0 +1,161 @@ +// Copyright 2016 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ +#define DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ + +#include + +#include "draco/mesh/corner_table.h" + +namespace draco { + +// Class for detecting connected components on an input mesh defined by a +// corner table. Degenerated faces and their vertices are not assigned to any +// component. +class MeshConnectedComponents { + public: + MeshConnectedComponents() = default; + + // Initializes the class with the component data of the input mesh. No other + // method should be called before this one. + template + void FindConnectedComponents(const CornerTableT *corner_table); + int NumConnectedComponents() const { return components_.size(); } + + struct ConnectedComponent { + std::vector vertices; + std::vector faces; + std::vector boundary_edges; + }; + + const ConnectedComponent &GetConnectedComponent(int index) const { + return components_[index]; + } + + // Returns the id of an component attached to a given vertex. Returns -1 when + // the vertex was not assigned to any component. + int GetConnectedComponentIdAtVertex(int vertex_id) const { + return vertex_to_component_map_[vertex_id]; + } + + // Returns the number of vertices that belong to the input component. + int NumConnectedComponentVertices(int component_id) const { + return components_[component_id].vertices.size(); + } + + // Returns the i-th vertex of the input component. + int GetConnectedComponentVertex(int component_id, int i) const { + return components_[component_id].vertices[i]; + } + + // Returns the id of an component attached to a given face. Returns -1 when + // the face was not assigned to any component. + int GetConnectedComponentIdAtFace(int face_id) const { + return face_to_component_map_[face_id]; + } + + // Returns the number of faces that belong to the input component. + int NumConnectedComponentFaces(int component_id) const { + return components_[component_id].faces.size(); + } + + // Returns the i-th face of the input component. + int GetConnectedComponentFace(int component_id, int i) const { + return components_[component_id].faces[i]; + } + + // Returns the number of boundary edges that belong to the input component. + int NumConnectedComponentBoundaryEdges(int component_id) const { + return components_[component_id].boundary_edges.size(); + } + + // Returns the i-th boundary edge of the input component. + int GetConnectedComponentBoundaryEdge(int component_id, int i) const { + return components_[component_id].boundary_edges[i]; + } + + private: + std::vector vertex_to_component_map_; + std::vector face_to_component_map_; + std::vector boundary_corner_to_component_map_; + std::vector components_; +}; + +template +void MeshConnectedComponents::FindConnectedComponents( + const CornerTableT *corner_table) { + components_.clear(); + vertex_to_component_map_.assign(corner_table->num_vertices(), -1); + face_to_component_map_.assign(corner_table->num_faces(), -1); + boundary_corner_to_component_map_.assign(corner_table->num_corners(), -1); + std::vector is_face_visited(corner_table->num_faces(), false); + std::vector face_stack; + // Go over all faces of the mesh and for each unvisited face, recursively + // traverse its neighborhood and mark all traversed faces as visited. All + // faces visited during one traversal belong to one mesh component. + for (int face_id = 0; face_id < corner_table->num_faces(); ++face_id) { + if (is_face_visited[face_id]) { + continue; + } + if (corner_table->IsDegenerated(FaceIndex(face_id))) { + continue; + } + const int component_id = components_.size(); + components_.push_back(ConnectedComponent()); + face_stack.push_back(face_id); + is_face_visited[face_id] = true; + while (!face_stack.empty()) { + const int act_face_id = face_stack.back(); + if (face_to_component_map_[act_face_id] == -1) { + face_to_component_map_[act_face_id] = component_id; + components_[component_id].faces.push_back(act_face_id); + } + face_stack.pop_back(); + // Gather all neighboring faces. + std::array corners = + corner_table->AllCorners(FaceIndex(act_face_id)); + for (int c = 0; c < 3; ++c) { + // Update vertex to component mapping. + const int vertex_id = corner_table->Vertex(corners[c]).value(); + if (vertex_to_component_map_[vertex_id] == -1) { + vertex_to_component_map_[vertex_id] = component_id; + components_[component_id].vertices.push_back(vertex_id); + } + // Traverse component to neighboring faces (add the faces to the stack). + const CornerIndex opp_corner = corner_table->Opposite(corners[c]); + if (opp_corner == kInvalidCornerIndex) { + if (boundary_corner_to_component_map_[corners[c].value()] == -1) { + boundary_corner_to_component_map_[corners[c].value()] = + component_id; + components_[component_id].boundary_edges.push_back( + corners[c].value()); + } + continue; // Invalid corner (mesh boundary). + } + + const int opp_face_id = corner_table->Face(opp_corner).value(); + if (is_face_visited[opp_face_id]) { + continue; // Opposite face has been already reached. + } + is_face_visited[opp_face_id] = true; + face_stack.push_back(opp_face_id); + } + } + } +} + +} // namespace draco + +#endif // DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.cc new file mode 100644 index 000000000..f859ae411 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.cc @@ -0,0 +1,98 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_features.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +MeshFeatures::MeshFeatures() + : feature_count_(0), + null_feature_id_(-1), + attribute_index_(-1), + property_table_index_(-1) {} + +void MeshFeatures::Copy(const MeshFeatures &src) { + label_ = src.label_; + feature_count_ = src.feature_count_; + null_feature_id_ = src.null_feature_id_; + attribute_index_ = src.attribute_index_; + texture_map_.Copy(src.texture_map_); + texture_channels_ = src.texture_channels_; + property_table_index_ = src.property_table_index_; +} + +void MeshFeatures::SetLabel(const std::string &label) { label_ = label; } + +const std::string &MeshFeatures::GetLabel() const { return label_; } + +void MeshFeatures::SetFeatureCount(int feature_count) { + feature_count_ = feature_count; +} + +int MeshFeatures::GetFeatureCount() const { return feature_count_; } + +void MeshFeatures::SetNullFeatureId(int null_feature_id) { + null_feature_id_ = null_feature_id; +} + +int MeshFeatures::GetNullFeatureId() const { return null_feature_id_; } + +void MeshFeatures::SetAttributeIndex(int attribute_index) { + attribute_index_ = attribute_index; +} + +int MeshFeatures::GetAttributeIndex() const { return attribute_index_; } + +void MeshFeatures::SetTextureMap(const TextureMap &texture_map) { + texture_map_.Copy(texture_map); +} + +void MeshFeatures::SetTextureMap(Texture *texture, int tex_coord_index) { + texture_map_.SetProperties(TextureMap::GENERIC, tex_coord_index); + texture_map_.SetTexture(texture); +} + +const TextureMap &MeshFeatures::GetTextureMap() const { return texture_map_; } + +TextureMap &MeshFeatures::GetTextureMap() { return texture_map_; } + +void MeshFeatures::SetTextureChannels( + const std::vector &texture_channels) { + texture_channels_ = texture_channels; +} + +const std::vector &MeshFeatures::GetTextureChannels() const { + return texture_channels_; +} + +std::vector &MeshFeatures::GetTextureChannels() { + return texture_channels_; +} + +void MeshFeatures::SetPropertyTableIndex(int property_table_index) { + property_table_index_ = property_table_index; +} + +int MeshFeatures::GetPropertyTableIndex() const { + return property_table_index_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.h new file mode 100644 index 000000000..af024013f --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features.h @@ -0,0 +1,93 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_FEATURES_H_ +#define DRACO_MESH_MESH_FEATURES_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Describes a mesh feature ID set according to the EXT_mesh_features glTF +// extension. Feature IDs are either associated with geometry vertices or with +// texture pixels and stored in a geometry attribute or in texture channels, +// respectively. Optionally, the feature ID set may be associated with a +// property table defined in the EXT_structural_metadata glTF extension. +class MeshFeatures { + public: + // Creates an empty feature ID set that is associated neither with vertices, + // nor with texture pixels, nor with property tables. + MeshFeatures(); + + // Copies all data from |src| mesh feature ID set. + void Copy(const MeshFeatures &src); + + // Label assigned to this feature ID set. + void SetLabel(const std::string &label); + const std::string &GetLabel() const; + + // The number of unique features in this feature ID set. + void SetFeatureCount(int feature_count); + int GetFeatureCount() const; + + // Non-negative null feature ID value indicating the absence of an associated + // feature. The value of -1 indicates that the null feature ID is not set. + void SetNullFeatureId(int null_feature_id); + int GetNullFeatureId() const; + + // Index of the feature ID vertex attribute, e.g., 5 for an attribute named + // _FEATURE_ID_5, or -1 if the feature ID is not associated with vertices. + void SetAttributeIndex(int attribute_index); + int GetAttributeIndex() const; + + // Feature ID texture map and texture channels containing feature IDs + // associated with texture pixels. Only used when |attribute_index_| is -1. + // The RGBA channels are numbered from 0 to 3. See the glTF extension + // documentation for reconstruction of feature ID from the channel values. + void SetTextureMap(const TextureMap &texture_map); + void SetTextureMap(Texture *texture, int tex_coord_index); + const TextureMap &GetTextureMap() const; + TextureMap &GetTextureMap(); + void SetTextureChannels(const std::vector &texture_channels); + const std::vector &GetTextureChannels() const; + std::vector &GetTextureChannels(); + + // Non-negative index of the property table this feature ID set is associated + // with. Property tables are defined in the EXT_structural_metadata glTF + // extension. The value of -1 indicates that this feature ID set is not + // associated with any property tables. + void SetPropertyTableIndex(int property_table_index); + int GetPropertyTableIndex() const; + + private: + std::string label_; + int feature_count_; + int null_feature_id_; + int attribute_index_; + TextureMap texture_map_; + std::vector texture_channels_; + int property_table_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_FEATURES_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features_test.cc new file mode 100644 index 000000000..0e67af2b1 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_features_test.cc @@ -0,0 +1,98 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_features.h" + +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/texture/texture_map.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(MeshFeaturesTest, TestDefaults) { + // Test construction of an empty feature ID set. + draco::MeshFeatures mesh_features; + ASSERT_TRUE(mesh_features.GetLabel().empty()); + ASSERT_EQ(mesh_features.GetFeatureCount(), 0); + ASSERT_EQ(mesh_features.GetNullFeatureId(), -1); + ASSERT_EQ(mesh_features.GetAttributeIndex(), -1); + ASSERT_EQ(mesh_features.GetPropertyTableIndex(), -1); + ASSERT_TRUE(mesh_features.GetTextureChannels().empty()); + ASSERT_EQ(mesh_features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(mesh_features.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +TEST(MeshFeaturesTest, TestSettersAndGetters) { + // Test setter and getter methods of the feature ID set. + draco::MeshFeatures mesh_features; + mesh_features.SetLabel("continent"); + mesh_features.SetFeatureCount(8); + mesh_features.SetNullFeatureId(0); + mesh_features.SetAttributeIndex(2); + mesh_features.SetPropertyTableIndex(10); + std::vector channels = {2, 3}; + mesh_features.SetTextureChannels({2, 3}); + draco::TextureMap texture_map; + texture_map.SetProperties(draco::TextureMap::GENERIC, 1); + std::unique_ptr texture(new draco::Texture()); + texture_map.SetTexture(texture.get()); + mesh_features.SetTextureMap(texture_map); + + // Check that mesh feature set properties can be accessed via getters. + ASSERT_EQ(mesh_features.GetLabel(), "continent"); + ASSERT_EQ(mesh_features.GetFeatureCount(), 8); + ASSERT_EQ(mesh_features.GetNullFeatureId(), 0); + ASSERT_EQ(mesh_features.GetAttributeIndex(), 2); + ASSERT_EQ(mesh_features.GetPropertyTableIndex(), 10); + ASSERT_EQ(mesh_features.GetTextureChannels(), channels); + ASSERT_EQ(mesh_features.GetTextureMap().texture(), texture.get()); + ASSERT_EQ(mesh_features.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +TEST(MeshFeaturesTest, TestCopy) { + // Test that feature ID set can be copied. + draco::MeshFeatures mesh_features; + mesh_features.SetLabel("continent"); + mesh_features.SetFeatureCount(8); + mesh_features.SetNullFeatureId(0); + mesh_features.SetAttributeIndex(2); + mesh_features.SetPropertyTableIndex(10); + std::vector channels = {2, 3}; + mesh_features.SetTextureChannels({2, 3}); + std::unique_ptr texture(new draco::Texture()); + mesh_features.SetTextureMap(texture.get(), 1); + + // Make a copy. + draco::MeshFeatures copy; + copy.Copy(mesh_features); + + // Check the copy. + ASSERT_EQ(copy.GetLabel(), "continent"); + ASSERT_EQ(copy.GetFeatureCount(), 8); + ASSERT_EQ(copy.GetNullFeatureId(), 0); + ASSERT_EQ(copy.GetAttributeIndex(), 2); + ASSERT_EQ(copy.GetPropertyTableIndex(), 10); + ASSERT_EQ(copy.GetTextureChannels(), channels); + ASSERT_EQ(copy.GetTextureMap().texture(), texture.get()); + ASSERT_EQ(copy.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_indices.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_indices.h new file mode 100644 index 000000000..5df28d550 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_indices.h @@ -0,0 +1,37 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_MESH_MESH_INDICES_H_ +#define DRACO_MESH_MESH_INDICES_H_ + +#include + +#include + +#include "draco/core/draco_index_type.h" + +namespace draco { + +// Index of a mesh feature ID set. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshFeaturesIndex) + +// Constants denoting invalid indices. +static constexpr MeshFeaturesIndex kInvalidMeshFeaturesIndex( + std::numeric_limits::max()); + +} // namespace draco + +#endif // DRACO_MESH_MESH_INDICES_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_misc_functions.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_misc_functions.h index b450bc80c..0a3bcf497 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_misc_functions.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_misc_functions.h @@ -67,7 +67,6 @@ inline bool IsCornerOppositeToAttributeSeam(CornerIndex ci, // Interpolates an attribute value on a face using given barycentric // coordinates. InterpolatedVectorT should be a VectorD that corresponds to the // values stored in the attribute. -// TODO(ostava): Find a better place for this. template InterpolatedVectorT ComputeInterpolatedAttributeValueOnMeshFace( const Mesh &mesh, const PointAttribute &attribute, FaceIndex fi, diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.cc new file mode 100644 index 000000000..ac3c4661c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.cc @@ -0,0 +1,451 @@ +// Copyright 2017 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_splitter.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/mesh/mesh_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/point_cloud/point_cloud_builder.h" + +namespace draco { + +// Helper class that handles splitting of meshes with faces / without faces, +// i.e. point clouds. +template +class MeshSplitterInternal { + public: + struct WorkData : public MeshSplitter::WorkData { + // TriangleSoupMeshBuilder or PointCloudBuilder. + std::vector builders; + }; + + // Computes number of elements (faces or points) for each sub-mesh. + Status InitializeWorkDataNumElements(const Mesh &mesh, int split_attribute_id, + WorkData *work_data) const; + // Initializes a builder for a given sub-mesh. + void InitializeBuilder(int b_index, int num_elements, const Mesh &mesh, + int ignored_attribute_id, WorkData *work_data) const; + // Add all faces or points to the builders. + void AddElementsToBuilder(const Mesh &mesh, + const PointAttribute *split_attribute, + WorkData *work_data) const; + // Builds the meshes from the data accumulated in the builders. + StatusOr BuildMeshes(const Mesh &mesh, + WorkData *work_data) const; +}; + +namespace { + +// Helper functions for copying single element from source |mesh| to a target +// builder |b_index| stored in |work_data|. +void AddElementToBuilder( + int b_index, FaceIndex source_i, FaceIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data); +void AddElementToBuilder( + int b_index, PointIndex source_i, PointIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data); +} // namespace + +MeshSplitter::MeshSplitter() + : preserve_materials_(false), + remove_unused_material_indices_(true), + preserve_mesh_features_(false) {} + +StatusOr MeshSplitter::SplitMesh( + const Mesh &mesh, uint32_t split_attribute_id) { + if (mesh.num_attributes() <= split_attribute_id) { + return Status(Status::DRACO_ERROR, "Invalid attribute id."); + } + if (mesh.num_faces() == 0) { + return SplitMeshInternal(mesh, split_attribute_id); + } else { + return SplitMeshInternal(mesh, split_attribute_id); + } +} + +template +StatusOr MeshSplitter::SplitMeshInternal( + const Mesh &mesh, int split_attribute_id) { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + + // Preserve the split attribute only if it is the material attribute and the + // |preserve_materials_| flag is set. Othwerwise the split attribute will get + // discarded. + // TODO(ostava): We may revisit this later and add an option to always + // preserve the split attribute. + const bool preserve_split_attribute = + preserve_materials_ && + split_attribute->attribute_type() == GeometryAttribute::MATERIAL; + + const int num_out_meshes = split_attribute->size(); + MeshSplitterInternal splitter_internal; + typename MeshSplitterInternal::WorkData work_data; + work_data.num_sub_mesh_elements.resize(num_out_meshes, 0); + work_data.split_by_materials = + (split_attribute->attribute_type() == GeometryAttribute::MATERIAL); + + DRACO_RETURN_IF_ERROR(splitter_internal.InitializeWorkDataNumElements( + mesh, split_attribute_id, &work_data)); + + // Create the sub-meshes. + work_data.builders.resize(num_out_meshes); + // Map between attribute ids of the input and output meshes. + work_data.att_id_map.resize(mesh.num_attributes(), -1); + const int ignored_att_id = + (!preserve_split_attribute ? split_attribute_id : -1); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data.num_sub_mesh_elements[mi] == 0) { + continue; // Empty mesh, don't initialize it. + } + + const int num_elements = work_data.num_sub_mesh_elements[mi]; + splitter_internal.InitializeBuilder(mi, num_elements, mesh, ignored_att_id, + &work_data); + + // Reset the element counter for the sub-mesh. It will be used to keep track + // of number of elements added to the sub-mesh. + work_data.num_sub_mesh_elements[mi] = 0; + } + + splitter_internal.AddElementsToBuilder(mesh, split_attribute, &work_data); + + DRACO_ASSIGN_OR_RETURN(MeshVector out_meshes, + splitter_internal.BuildMeshes(mesh, &work_data)); + return FinalizeMeshes(mesh, work_data, std::move(out_meshes)); +} + +template <> +Status +MeshSplitterInternal::InitializeWorkDataNumElements( + const Mesh &mesh, int split_attribute_id, WorkData *work_data) const { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + // Verify that the attribute values are defined "per-face", i.e., all points + // on a face are always mapped to the same attribute value. + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto face = mesh.face(fi); + const AttributeValueIndex avi = split_attribute->mapped_index(face[0]); + for (int c = 1; c < 3; ++c) { + if (split_attribute->mapped_index(face[c]) != avi) { + return Status(Status::DRACO_ERROR, + "Attribute values not consistent on a face."); + } + } + work_data->num_sub_mesh_elements[avi.value()] += 1; + } + return OkStatus(); +} + +template <> +Status MeshSplitterInternal::InitializeWorkDataNumElements( + const Mesh &mesh, int split_attribute_id, WorkData *work_data) const { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + // Each point can have a different value. Just accumulate the number of points + // with the same attribute value index. + for (PointIndex pi(0); pi < mesh.num_points(); ++pi) { + const AttributeValueIndex avi = split_attribute->mapped_index(pi); + work_data->num_sub_mesh_elements[avi.value()] += 1; + } + return OkStatus(); +} + +template +void MeshSplitterInternal::InitializeBuilder( + int b_index, int num_elements, const Mesh &mesh, int ignored_attribute_id, + WorkData *work_data) const { + work_data->builders[b_index].Start(num_elements); + + // Add all attributes. + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + if (ai == ignored_attribute_id) { + continue; + } + const GeometryAttribute *const src_att = mesh.attribute(ai); + work_data->att_id_map[ai] = work_data->builders[b_index].AddAttribute( + src_att->attribute_type(), src_att->num_components(), + src_att->data_type()); + } +} + +template <> +void MeshSplitterInternal::AddElementsToBuilder( + const Mesh &mesh, const PointAttribute *split_attribute, + WorkData *work_data) const { + // Go over all faces of the input mesh and add them to the appropriate + // sub-mesh. + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto face = mesh.face(fi); + const int sub_mesh_id = split_attribute->mapped_index(face[0]).value(); + const FaceIndex target_fi(work_data->num_sub_mesh_elements[sub_mesh_id]++); + AddElementToBuilder(sub_mesh_id, fi, target_fi, mesh, work_data); + } +} + +template <> +void MeshSplitterInternal::AddElementsToBuilder( + const Mesh &mesh, const PointAttribute *split_attribute, + WorkData *work_data) const { + // Go over all points of the input mesh and add them to the appropriate + // sub-mesh. + for (PointIndex pi(0); pi < mesh.num_points(); ++pi) { + const int sub_mesh_id = split_attribute->mapped_index(pi).value(); + const PointIndex target_pi(work_data->num_sub_mesh_elements[sub_mesh_id]++); + AddElementToBuilder(sub_mesh_id, pi, target_pi, mesh, work_data); + } +} + +namespace { + +void AddElementToBuilder( + int b_index, FaceIndex source_i, FaceIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data) { + const auto &face = mesh.face(source_i); + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + const PointAttribute *const src_att = mesh.attribute(ai); + const int target_att_id = work_data->att_id_map[ai]; + if (target_att_id == -1) { + continue; + } + // Add value for each corner of the face. + work_data->builders[b_index].SetAttributeValuesForFace( + target_att_id, target_i, src_att->GetAddressOfMappedIndex(face[0]), + src_att->GetAddressOfMappedIndex(face[1]), + src_att->GetAddressOfMappedIndex(face[2])); + } +} + +void AddElementToBuilder( + int b_index, PointIndex source_i, PointIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data) { + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + const PointAttribute *const src_att = mesh.attribute(ai); + const int target_att_id = work_data->att_id_map[ai]; + if (target_att_id == -1) { + continue; + } + // Add value for the point |target_i|. + work_data->builders[b_index].SetAttributeValueForPoint( + target_att_id, target_i, src_att->GetAddressOfMappedIndex(source_i)); + } +} + +} // namespace + +template <> +StatusOr +MeshSplitterInternal::BuildMeshes( + const Mesh &mesh, WorkData *work_data) const { + const int num_out_meshes = work_data->builders.size(); + MeshSplitter::MeshVector out_meshes(num_out_meshes); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data->num_sub_mesh_elements[mi] == 0) { + continue; + } + out_meshes[mi] = work_data->builders[mi].Finalize(); + if (out_meshes[mi] == nullptr) { + continue; + } + } + return out_meshes; +} + +template <> +StatusOr +MeshSplitterInternal::BuildMeshes( + const Mesh &mesh, WorkData *work_data) const { + const int num_out_meshes = work_data->builders.size(); + MeshSplitter::MeshVector out_meshes(num_out_meshes); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data->num_sub_mesh_elements[mi] == 0) { + continue; + } + // For point clouds, we first build a point cloud and copy it over into + // a draco::Mesh. + std::unique_ptr pc = work_data->builders[mi].Finalize(true); + if (pc == nullptr) { + continue; + } + std::unique_ptr mesh(new Mesh()); + PointCloud *mesh_pc = mesh.get(); + mesh_pc->Copy(*pc); + out_meshes[mi] = std::move(mesh); + } + return out_meshes; +} + +StatusOr MeshSplitter::FinalizeMeshes( + const Mesh &mesh, const WorkData &work_data, MeshVector out_meshes) const { + // Finalize meshes. + const int num_out_meshes = out_meshes.size(); + + // If we are going to preserve mesh features, we will need to update texture + // pointers for all mesh feature textures. Here we store the mapping between + // the old texture pointers and their indices. + std::unordered_map features_texture_to_index_map; + if (preserve_mesh_features_) { + features_texture_to_index_map = + mesh.GetNonMaterialTextureLibrary().ComputeTextureToIndexMap(); + } + + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (out_meshes[mi] == nullptr) { + continue; + } + out_meshes[mi]->SetName(mesh.GetName()); + if (preserve_materials_) { + out_meshes[mi]->GetMaterialLibrary().Copy(mesh.GetMaterialLibrary()); + } + + // Copy metadata of the original mesh to the output meshes. + if (mesh.GetMetadata() != nullptr) { + const GeometryMetadata &metadata = *mesh.GetMetadata(); + out_meshes[mi]->AddMetadata( + std::unique_ptr(new GeometryMetadata(metadata))); + } + + // Copy over attribute unique ids. + for (int att_id = 0; att_id < mesh.num_attributes(); ++att_id) { + const int mapped_att_id = work_data.att_id_map[att_id]; + if (mapped_att_id == -1) { + continue; + } + const PointAttribute *const src_att = mesh.attribute(att_id); + PointAttribute *const dst_att = out_meshes[mi]->attribute(mapped_att_id); + dst_att->set_unique_id(src_att->unique_id()); + } + + // Copy compression settings of the original mesh to the output meshes. + out_meshes[mi]->SetCompressionEnabled(mesh.IsCompressionEnabled()); + out_meshes[mi]->SetCompressionOptions(mesh.GetCompressionOptions()); + + if (preserve_mesh_features_) { + // Copy mesh features from the source |mesh| to the |out_meshes[mi]|. + for (MeshFeaturesIndex mfi(0); mfi < mesh.NumMeshFeatures(); ++mfi) { + if (work_data.split_by_materials) { + // Copy over only those mesh features that were masked to the material + // corresponding to |mi|. + bool is_used = false; + if (mesh.NumMeshFeaturesMaterialMasks(mfi) == 0) { + is_used = true; + } else { + for (int mask_index = 0; + mask_index < mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + if (mesh.GetMeshFeaturesMaterialMask(mfi, mask_index) == mi) { + is_used = true; + break; + } + } + } + if (!is_used) { + // Ignore this mesh features. + continue; + } + } + // Create a copy of source mesh features. + std::unique_ptr mf(new MeshFeatures()); + mf->Copy(mesh.GetMeshFeatures(mfi)); + const MeshFeaturesIndex new_mfi = + out_meshes[mi]->AddMeshFeatures(std::move(mf)); + if (work_data.split_by_materials && !preserve_materials_) { + // If the input |mesh| was split by materials and we didn't preserve + // the materials, all mesh features must be masked to material 0. + out_meshes[mi]->AddMeshFeaturesMaterialMask(new_mfi, 0); + } else { + // Otherwise mesh features use same masking as the source mesh because + // the material attribute is still present in the split meshes. + // Note that this masking can be later changed in + // RemoveUnusedMaterials() call below. + for (int mask_index = 0; + mask_index < mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + out_meshes[mi]->AddMeshFeaturesMaterialMask( + new_mfi, mesh.GetMeshFeaturesMaterialMask(mfi, mask_index)); + } + } + } + + // Copy over all features textures to the split mesh. + out_meshes[mi]->GetNonMaterialTextureLibrary().Copy( + mesh.GetNonMaterialTextureLibrary()); + + // Update mesh features texture pointers to the new library. + for (MeshFeaturesIndex mfi(0); mfi < out_meshes[mi]->NumMeshFeatures(); + ++mfi) { + Mesh::UpdateMeshFeaturesTexturePointer( + features_texture_to_index_map, + &out_meshes[mi]->GetNonMaterialTextureLibrary(), + &out_meshes[mi]->GetMeshFeatures(mfi)); + } + + // This will remove any mesh features that may not be be actually used + // by this |out_meshes[mi]| (e.g. because corresponding material indices + // were not present in this split mesh). This also removes any unused + // features textures from the non-material texture library. + DRACO_RETURN_IF_ERROR( + MeshUtils::RemoveUnusedMeshFeatures(out_meshes[mi].get())); + } + + // Remove unused materials after we remove mesh features because some of + // the mesh features may have referenced old material indices. + if (preserve_materials_) { + out_meshes[mi]->RemoveUnusedMaterials(remove_unused_material_indices_); + } + + // Copy structural metadata from input mesh to each of the output meshes. + out_meshes[mi]->GetStructuralMetadata().Copy(mesh.GetStructuralMetadata()); + } + return std::move(out_meshes); +} + +StatusOr MeshSplitter::SplitMeshToComponents( + const Mesh &mesh, const MeshConnectedComponents &connected_components) { + // Create the sub-meshes. + const int num_out_meshes = connected_components.NumConnectedComponents(); + MeshSplitterInternal splitter_internal; + typename MeshSplitterInternal::WorkData work_data; + work_data.builders.resize(num_out_meshes); + work_data.num_sub_mesh_elements.resize(num_out_meshes, 0); + work_data.att_id_map.resize(mesh.num_attributes(), -1); + for (int mi = 0; mi < num_out_meshes; ++mi) { + const int num_faces = connected_components.NumConnectedComponentFaces(mi); + work_data.num_sub_mesh_elements[mi] = num_faces; + splitter_internal.InitializeBuilder(mi, num_faces, mesh, -1, &work_data); + } + + // Go over all faces of the input mesh and add them to the appropriate + // sub-mesh. + for (int mi = 0; mi < num_out_meshes; ++mi) { + for (int cfi = 0; cfi < connected_components.NumConnectedComponentFaces(mi); + ++cfi) { + const FaceIndex fi( + connected_components.GetConnectedComponent(mi).faces[cfi]); + const FaceIndex target_fi(cfi); + AddElementToBuilder(mi, fi, target_fi, mesh, &work_data); + } + } + DRACO_ASSIGN_OR_RETURN(auto out_meshes, + splitter_internal.BuildMeshes(mesh, &work_data)); + return FinalizeMeshes(mesh, work_data, std::move(out_meshes)); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.h new file mode 100644 index 000000000..bf5cd9794 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter.h @@ -0,0 +1,109 @@ +// Copyright 2017 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_SPLITTER_H_ +#define DRACO_MESH_MESH_SPLITTER_H_ + +#include +#include + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status_or.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/mesh_connected_components.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" + +namespace draco { + +// Class that can be used to split a single mesh into multiple sub-meshes +// according to specified criteria. +class MeshSplitter { + public: + typedef std::vector> MeshVector; + MeshSplitter(); + + // Sets a flag that tells the splitter to preserve all materials on the input + // mesh during mesh splitting. When set, the materials used on sub-meshes are + // going to be copied over. Any redundant materials on sub-meshes are going to + // be deleted but material indices may still be preserved depending on the + // SetRemoveUnusedMaterialIndices() flag. + // Default = false. + void SetPreserveMaterials(bool flag) { preserve_materials_ = flag; } + + // Sets a flag that tells the splitter to delete any unused material indices + // on the generated sub-meshes. This option is currently used only when + // SetPreserveMaterials() was set to true. If this option is set to false, the + // material indices of the MATERIAL attribute will be the same as in the + // source mesh. If the flag is true, then the unused material indices will be + // removed and they may no longer correspond to the source mesh. Note that + // when this flag is false, any unused materials would be replaced with empty + // (default) materials. + // Default = true. + void SetRemoveUnusedMaterialIndices(bool flag) { + remove_unused_material_indices_ = flag; + } + + // Sets a flag that tells the splitter to preserve all mesh features on the + // input mesh during mesh splitting. When set, the mesh features used on + // sub-meshes are going to be copied over. Any redundant mesh features on + // sub-meshes are going to be deleted. + // Default = false. + void SetPreserveMeshFeatures(bool flag) { preserve_mesh_features_ = flag; } + + // Splits the input |mesh| according to attribute values stored in the + // specified attribute. If the |mesh| contains faces, the attribute values + // need to be defined per-face, that is, all points attached to a single face + // must share the same attribute value. Meshes without faces are treated as + // point clouds and the attribute values can be defined per-point. Each + // attribute value (AttributeValueIndex) is mapped to a single output mesh. If + // an AttributeValueIndex is unused, no mesh is created for the given value. + StatusOr SplitMesh(const Mesh &mesh, uint32_t split_attribute_id); + + // Splits the input |mesh| into separate components defined in + // |connected_components|. That is, all faces associated with a given + // component index will be stored in the same mesh. The number of generated + // meshes will correspond to |connected_components.NumConnectedComponents()|. + StatusOr SplitMeshToComponents( + const Mesh &mesh, const MeshConnectedComponents &connected_components); + + private: + struct WorkData { + // Map between attribute ids of the input and output meshes. + std::vector att_id_map; + std::vector num_sub_mesh_elements; + bool split_by_materials = false; + }; + + template + StatusOr SplitMeshInternal(const Mesh &mesh, + int split_attribute_id); + + StatusOr FinalizeMeshes(const Mesh &mesh, + const WorkData &work_data, + MeshVector out_meshes) const; + + bool preserve_materials_; + bool remove_unused_material_indices_; + bool preserve_mesh_features_; + + template + friend class MeshSplitterInternal; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_SPLITTER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter_test.cc similarity index 51% rename from Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc rename to Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter_test.cc index 29e7ed3ba..7432c4736 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_splitter_test.cc @@ -12,14 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// This file is used by emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -#include "draco/attributes/geometry_attribute.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/encode.h" -#include "draco/javascript/emscripten/animation_encoder_webidl_wrapper.h" -#include "draco/mesh/mesh.h" -#include "draco/point_cloud/point_cloud.h" +#include "draco/mesh/mesh_splitter.h" -// glue_animation_encoder.cpp is generated by Makefile.emcc build_glue target. -#include "glue_animation_encoder.cpp" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/vector_d.h" +#include "draco/io/mesh_io.h" +#include "draco/mesh/mesh_misc_functions.h" + +namespace {} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_stripifier.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_stripifier.h index 262e3c792..8e8d8d9f2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_stripifier.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_stripifier.h @@ -71,8 +71,6 @@ class MeshStripifier { mesh_ = &mesh; num_strips_ = 0; num_encoded_faces_ = 0; - // TODO(ostava): We may be able to avoid computing the corner table if we - // already have it stored somewhere. corner_table_ = CreateCornerTableFromPositionAttribute(mesh_); if (corner_table_ == nullptr) { return false; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_test.cc new file mode 100644 index 000000000..7cc046a7e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_test.cc @@ -0,0 +1,644 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh.h" + +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/material/material_utils.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#endif // DRACO_TRANSCODER_SUPPORTED + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +// Tests naming of a mesh. +TEST(MeshTest, MeshName) { + draco::Mesh mesh; + ASSERT_TRUE(mesh.GetName().empty()); + mesh.SetName("Bob"); + ASSERT_EQ(mesh.GetName(), "Bob"); +} + +// Tests copying of a mesh. +TEST(MeshTest, MeshCopy) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); +} + +// Tests that we can copy a mesh to a different mesh that already contains some +// data. +TEST(MeshTest, MeshCopyToExistingMesh) { + const std::unique_ptr mesh_0 = + draco::ReadMeshFromTestFile("cube_att.obj"); + const std::unique_ptr mesh_1 = + draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh_0, nullptr); + ASSERT_NE(mesh_1, nullptr); + draco::MeshAreEquivalent eq; + ASSERT_FALSE(eq(*mesh_0, *mesh_1)); + + mesh_1->Copy(*mesh_0); + ASSERT_TRUE(eq(*mesh_0, *mesh_1)); +} + +// Tests that we can remove unused materials from a mesh. +TEST(MeshTest, RemoveUnusedMaterials) { + // Input mesh has 29 materials defined in the source file but only 7 are + // actually used. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Get materials on all faces. + std::vector face_materials(mesh->num_faces(), + nullptr); + for (draco::FaceIndex fi(0); fi < mesh->num_faces(); ++fi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(mesh->face(fi)[0], &mat_index); + face_materials[fi.value()] = + mesh->GetMaterialLibrary().GetMaterial(mat_index); + } + + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 7); + + // Ensure the material attribute contains material indices in the valid range. + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + ASSERT_LT(mat_index, mesh->GetMaterialLibrary().NumMaterials()); + } + + // Ensure all materials are still the same for all faces. + for (draco::FaceIndex fi(0); fi < mesh->num_faces(); ++fi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(mesh->face(fi)[0], &mat_index); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(mat_index), + face_materials[fi.value()]); + } +} + +TEST(MeshTest, RemoveUnusedMaterialsOnPointClud) { + // Input mesh has 29 materials defined in the source file but only 7 are + // actually used. Same as above test but we remove all faces and treat the + // model as a point cloud. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + // Make it a point cloud. + mesh->SetNumFaces(0); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Get materials on all points. + std::vector point_materials(mesh->num_points(), + nullptr); + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + point_materials[pi.value()] = + mesh->GetMaterialLibrary().GetMaterial(mat_index); + } + + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 7); + + // Ensure the material attribute contains material indices in the valid range. + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + ASSERT_LT(mat_index, mesh->GetMaterialLibrary().NumMaterials()); + } + + // Ensure all materials are still the same for all points. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(mat_index), + point_materials[pi.value()]); + } +} + +TEST(MeshTest, RemoveUnusedMaterialsNoIndices) { + // The same as above but we actually want to remove only materials and not + // material indices. Therefore we should end up with the same number of + // materials as source but all unused materials should be "default". + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Do not remove unused material indices. + mesh->RemoveUnusedMaterials(false); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 29); + + // Gether which materials were actually used and check that all remaining + // materials are "default". + std::vector is_mat_used(mesh->GetMaterialLibrary().NumMaterials(), + false); + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + is_mat_used[mat_index] = true; + } + + for (int mi = 0; mi < mesh->GetMaterialLibrary().NumMaterials(); ++mi) { + if (!is_mat_used[mi]) { + ASSERT_TRUE(draco::MaterialUtils::AreMaterialsEquivalent( + *mesh->GetMaterialLibrary().GetMaterial(mi), draco::Material())); + } + } +} + +TEST(MeshTest, TestAddNewAttributeWithConnectivity) { + // Tests that we can add new attributes with arbitrary connectivity to an + // existing mesh. + + // Create a simple quad. See corner indices of the quad on the figure below: + // + // *-------* + // |2\3 5| + // | \ | + // | \ | + // | \ | + // | \4| + // |0 1\| + // *-------* + // + draco::TriangleSoupMeshBuilder mb; + mb.Start(2); + mb.AddAttribute(draco::GeometryAttribute::POSITION, 3, draco::DT_FLOAT32); + mb.SetAttributeValuesForFace( + 0, draco::FaceIndex(0), draco::Vector3f(0, 0, 0).data(), + draco::Vector3f(1, 0, 0).data(), draco::Vector3f(1, 1, 0).data()); + mb.SetAttributeValuesForFace( + 0, draco::FaceIndex(1), draco::Vector3f(1, 1, 0).data(), + draco::Vector3f(1, 0, 0).data(), draco::Vector3f(1, 1, 1).data()); + std::unique_ptr mesh = mb.Finalize(); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_points(), 4); + ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION)->size(), + 4); + + // Create a simple attribute that has a constant value on every corner. + std::unique_ptr pa(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One components*/, + draco::DT_UINT8, false, 1); + uint8_t val = 10; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + + // Map all corners to the same value. + draco::IndexTypeVector + corner_to_point(6, draco::AttributeValueIndex(0)); + + // Adding this attribute to the mesh should not increase the number of points. + const int new_att_id_0 = + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + ASSERT_EQ(mesh->num_attributes(), 2); + ASSERT_EQ(mesh->num_points(), 4); + + const draco::PointAttribute *const new_att_0 = mesh->attribute(new_att_id_0); + ASSERT_NE(new_att_0, nullptr); + + // All points of the mesh should be mapped to the same attribute value. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint8_t att_val = 0; + new_att_0->GetMappedValue(pi, &att_val); + ASSERT_EQ(att_val, 10); + } + + // Add a new attribute with two values and different connectivity. + pa = std::unique_ptr(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One components*/, + draco::DT_UINT8, false, 2); + val = 11; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + val = 12; + pa->SetAttributeValue(draco::AttributeValueIndex(1), &val); + + // Map all corners to the value index 0 except for corner 1 that is mapped to + // value index 1. This should result in a new point being created on either + // corner 1 or corner 4 (see figure at the beginning of this test). + corner_to_point.assign(6, draco::AttributeValueIndex(0)); + corner_to_point[draco::CornerIndex(1)] = draco::AttributeValueIndex(1); + + const int new_att_id_1 = + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + ASSERT_EQ(mesh->num_attributes(), 3); + + // One new point should have been created by adding the new attribute. + ASSERT_EQ(mesh->num_points(), 5); + + const draco::PointAttribute *const new_att_1 = mesh->attribute(new_att_id_1); + ASSERT_NE(new_att_1, nullptr); + ASSERT_TRUE(mesh->CornerToPointId(1) == draco::PointIndex(4) || + mesh->CornerToPointId(4) == draco::PointIndex(4)); + + new_att_1->GetMappedValue(mesh->CornerToPointId(1), &val); + ASSERT_EQ(val, 12); + + new_att_1->GetMappedValue(mesh->CornerToPointId(4), &val); + ASSERT_EQ(val, 11); + + // Ensure the attribute values of the remaining attributes are well defined + // on the new point. + draco::Vector3f pos; + mesh->attribute(0)->GetMappedValue(draco::PointIndex(4), &pos[0]); + ASSERT_EQ(pos, draco::Vector3f(1, 0, 0)); + + new_att_0->GetMappedValue(draco::PointIndex(4), &val); + ASSERT_EQ(val, 10); + + new_att_0->GetMappedValue(mesh->CornerToPointId(1), &val); + ASSERT_EQ(val, 10); + new_att_0->GetMappedValue(mesh->CornerToPointId(4), &val); + ASSERT_EQ(val, 10); +} + +TEST(MeshTest, TestAddNewAttributeWithConnectivityWithIsolatedVertices) { + // Tests that we can add a new attribute with connectivity to a mesh that + // contains isolated vertices. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("isolated_vertices.ply"); + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + ASSERT_TRUE(pos_att->is_mapping_identity()); + ASSERT_EQ(pos_att->size(), 5); + ASSERT_EQ(mesh->num_points(), 5); + ASSERT_EQ(mesh->num_faces(), 2); + + // Add a new attribute with two values (one for each face). + auto pa = std::unique_ptr(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One component*/, + draco::DT_UINT8, false, 2); + uint8_t val = 11; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + val = 12; + pa->SetAttributeValue(draco::AttributeValueIndex(1), &val); + + draco::IndexTypeVector + corner_to_point(6, draco::AttributeValueIndex(0)); + // All corners on the second face are mapped to the value 1. + for (draco::CornerIndex ci(3); ci < 6; ++ci) { + corner_to_point[ci] = draco::AttributeValueIndex(1); + } + + const draco::PointAttribute *const pa_raw = pa.get(); + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + // Two new point should have been added. + ASSERT_EQ(mesh->num_points(), 7); + + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + ASSERT_NE(pa_raw->mapped_index(pi), draco::kInvalidAttributeValueIndex); + ASSERT_NE(pos_att->mapped_index(pi), draco::kInvalidAttributeValueIndex); + } +} + +TEST(MeshTest, TestAddPerVertexAttribute) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + + // The input mesh should have 8 spatial vertices. + ASSERT_EQ(pos_att->size(), 8); + + // Add a new scalar attribute where each value corresponds to the position + // value index (vertex). + std::unique_ptr pa(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, draco::DT_FLOAT32, + false, /* one value per position value */ 8); + + // Set the value for the new attribute. + for (draco::AttributeValueIndex avi(0); avi < 8; ++avi) { + const float att_value = avi.value(); + pa->SetAttributeValue(avi, &att_value); + } + + // Add the attribute to the existing mesh. + const int new_att_id = mesh->AddPerVertexAttribute(std::move(pa)); + ASSERT_NE(new_att_id, -1); + + // Make sure all the attribute values are set correctly for every point of the + // mesh. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + const draco::AttributeValueIndex pos_avi = pos_att->mapped_index(pi); + const draco::AttributeValueIndex new_att_avi = + mesh->attribute(new_att_id)->mapped_index(pi); + ASSERT_EQ(pos_avi, new_att_avi); + + float new_att_value; + mesh->attribute(new_att_id)->GetValue(new_att_avi, &new_att_value); + ASSERT_EQ(new_att_value, new_att_avi.value()); + } +} + +TEST(MeshTest, TestRemovalOfIsolatedPoints) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("isolated_vertices.ply"); + + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + + ASSERT_EQ(mesh_copy.num_points(), 5); + mesh_copy.RemoveIsolatedPoints(); + ASSERT_EQ(mesh_copy.num_points(), 4); + + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); +} + +TEST(MeshTest, TestCompressionSettings) { + // Tests compression settings of a mesh. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Check that compression is disabled and compression settings are default. + ASSERT_FALSE(mesh->IsCompressionEnabled()); + const draco::DracoCompressionOptions default_compression_options; + ASSERT_EQ(mesh->GetCompressionOptions(), default_compression_options); + + // Check that compression options can be set without enabling compression. + draco::DracoCompressionOptions compression_options; + compression_options.quantization_bits_normal = 12; + mesh->SetCompressionOptions(compression_options); + ASSERT_EQ(mesh->GetCompressionOptions(), compression_options); + ASSERT_FALSE(mesh->IsCompressionEnabled()); + + // Check that compression can be enabled. + mesh->SetCompressionEnabled(true); + ASSERT_TRUE(mesh->IsCompressionEnabled()); + + // Check that individual compression options can be updated. + mesh->GetCompressionOptions().compression_level++; + mesh->GetCompressionOptions().compression_level--; + + // Check that compression settings can be copied. + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + ASSERT_TRUE(mesh_copy.IsCompressionEnabled()); + ASSERT_EQ(mesh_copy.GetCompressionOptions(), compression_options); +} + +// Tests adding and removing of mesh features to a mesh. +TEST(MeshTest, TestMeshFeatures) { + // Create a mesh with two feature ID sets. + draco::Mesh mesh; + ASSERT_EQ(mesh.NumMeshFeatures(), 0); + std::unique_ptr oceans(new draco::MeshFeatures()); + std::unique_ptr continents(new draco::MeshFeatures()); + oceans->SetLabel("oceans"); + continents->SetLabel("continents"); + const draco::MeshFeaturesIndex index_0 = + mesh.AddMeshFeatures(std::move(oceans)); + const draco::MeshFeaturesIndex index_1 = + mesh.AddMeshFeatures(std::move(continents)); + ASSERT_EQ(index_0, draco::MeshFeaturesIndex(0)); + ASSERT_EQ(index_1, draco::MeshFeaturesIndex(1)); + + // Check that the mesh has two feature ID sets. + ASSERT_EQ(mesh.NumMeshFeatures(), 2); + ASSERT_EQ(mesh.GetMeshFeatures(index_0).GetLabel(), "oceans"); + ASSERT_EQ(mesh.GetMeshFeatures(index_1).GetLabel(), "continents"); + + // Remove one feature ID set and check the remaining feature ID set. + mesh.RemoveMeshFeatures(draco::MeshFeaturesIndex(1)); + ASSERT_EQ(mesh.NumMeshFeatures(), 1); + ASSERT_EQ(mesh.GetMeshFeatures(draco::MeshFeaturesIndex(0)).GetLabel(), + "oceans"); + + // Remove the remaining feature ID set and check that no sets remain. + mesh.RemoveMeshFeatures(draco::MeshFeaturesIndex(0)); + ASSERT_EQ(mesh.NumMeshFeatures(), 0); +} + +// Tests copying of a mesh with feature ID sets. +TEST(MeshTest, MeshCopyWithMeshFeatures) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Add two textures to the non-material texture library of the mesh. + std::unique_ptr texture0(new draco::Texture()); + std::unique_ptr texture1(new draco::Texture()); + texture0->Resize(128, 128); + texture1->Resize(256, 256); + texture0->FillImage(draco::RGBA(100, 0, 0, 0)); + texture1->FillImage(draco::RGBA(200, 0, 0, 0)); + draco::TextureLibrary &library = mesh->GetNonMaterialTextureLibrary(); + library.PushTexture(std::move(texture0)); + library.PushTexture(std::move(texture1)); + + // Add feature ID set referring to an attribute. + const draco::MeshFeaturesIndex index_0 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_0).SetLabel("planet"); + mesh->GetMeshFeatures(index_0).SetFeatureCount(2); + mesh->GetMeshFeatures(index_0).SetAttributeIndex(1); + + // Add feature ID set referring to texture at index 0. + const draco::MeshFeaturesIndex index_1 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_1).SetLabel("continents"); + mesh->GetMeshFeatures(index_1).SetFeatureCount(7); + mesh->GetMeshFeatures(index_1).GetTextureMap().SetTexture( + library.GetTexture(0)); + + // Add feature ID set referring to a texture at index 1. + const draco::MeshFeaturesIndex index_2 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_2).SetLabel("oceans"); + mesh->GetMeshFeatures(index_2).SetFeatureCount(5); + mesh->GetMeshFeatures(index_2).GetTextureMap().SetTexture( + library.GetTexture(1)); + + // Check mesh feature ID set texture pointers. + ASSERT_EQ(library.NumTextures(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 3); + ASSERT_EQ(mesh->GetMeshFeatures(index_0).GetTextureMap().texture(), nullptr); + ASSERT_EQ(mesh->GetMeshFeatures(index_1).GetTextureMap().texture(), + library.GetTexture(0)); + ASSERT_EQ(mesh->GetMeshFeatures(index_2).GetTextureMap().texture(), + library.GetTexture(1)); + + // Copy the mesh. + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + + // Check that the meshes are equivalent. + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); + + // Also check that the texture pointers have been updated correctly. + const draco::TextureLibrary &library_copy = + mesh_copy.GetNonMaterialTextureLibrary(); + ASSERT_EQ(library_copy.NumTextures(), 2); + ASSERT_EQ(mesh_copy.NumMeshFeatures(), 3); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_0).GetTextureMap().texture(), + nullptr); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_1).GetTextureMap().texture(), + library_copy.GetTexture(0)); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_2).GetTextureMap().texture(), + library_copy.GetTexture(1)); +} + +// Tests copying of a mesh with structural metadata. +TEST(MeshTest, TestCopyWithStructuralMetadata) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Add structural metadata to the mesh. + draco::PropertyTable::Schema schema; + schema.json.SetString("Data"); + mesh->GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Copy the mesh. + draco::Mesh copy; + copy.Copy(*mesh); + + // Check that the structural metadata has been copied. + ASSERT_EQ( + copy.GetStructuralMetadata().GetPropertyTableSchema().json.GetString(), + "Data"); +} + +// Tests removing of unused materials for a mesh with mesh features. +TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf"); + ASSERT_NE(mesh, nullptr); + + // Input has five mesh features, two associated with material 0 and three with + // material 1. + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(3), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(4), 0), + 1); + + // Remove material 0. + draco::PointAttribute *mat_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL)); + // Map mat value 0 to 1. + uint32_t new_mat_index = 1; + mat_att->SetAttributeValue(draco::AttributeValueIndex(0), &new_mat_index); + + // This should not do anything because we still have the material 0 referenced + // by mesh features 0 and 1. + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + + // Now remove unused mesh features (should be 0 and 1). + DRACO_ASSERT_OK(draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get())); + + ASSERT_EQ(mesh->NumMeshFeatures(), 3); + // All remaining mesh features should be still mapped to material 1. + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 1); + + // Now remove the unused materials (0). + mesh->RemoveUnusedMaterials(); + + // Only one material should be remaining and all the mesh features should now + // be mapped to material 0. + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 0); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +// Test bounding box. +TEST(MeshTest, TestMeshBoundingBox) { + const draco::Vector3f max_pt(1, 1, 1); + const draco::Vector3f min_pt(0, 0, 0); + + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr) << "Failed in Loading: " + << "cube_att.obj"; + const draco::BoundingBox bounding_box = mesh->ComputeBoundingBox(); + + EXPECT_EQ(max_pt[0], bounding_box.GetMaxPoint()[0]); + EXPECT_EQ(max_pt[1], bounding_box.GetMaxPoint()[1]); + EXPECT_EQ(max_pt[2], bounding_box.GetMaxPoint()[2]); + + EXPECT_EQ(min_pt[0], bounding_box.GetMinPoint()[0]); + EXPECT_EQ(min_pt[1], bounding_box.GetMinPoint()[1]); + EXPECT_EQ(min_pt[2], bounding_box.GetMinPoint()[2]); +} + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.cc new file mode 100644 index 000000000..0fbe366c1 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.cc @@ -0,0 +1,492 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_utils.h" + +#include +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/attribute_quantization_transform.h" +#include "draco/core/quantization_utils.h" + +namespace draco { + +void MeshUtils::TransformMesh(const Eigen::Matrix4d &transform, Mesh *mesh) { + // Transform positions. + PointAttribute *pos_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::POSITION)); + for (AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + Vector3f pos_val; + pos_att->GetValue(avi, &pos_val[0]); + Eigen::Vector4d transformed_val(pos_val[0], pos_val[1], pos_val[2], 1); + transformed_val = transform * transformed_val; + pos_val = + Vector3f(transformed_val[0], transformed_val[1], transformed_val[2]); + pos_att->SetAttributeValue(avi, &pos_val[0]); + } + + // Transform normals and tangents. + PointAttribute *normal_att = nullptr; + PointAttribute *tangent_att = nullptr; + if (mesh->NumNamedAttributes(GeometryAttribute::NORMAL) > 0) { + normal_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::NORMAL)); + } + if (mesh->NumNamedAttributes(GeometryAttribute::TANGENT) > 0) { + tangent_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::TANGENT)); + } + + if (normal_att || tangent_att) { + // Use inverse-transpose matrix to transform normals and tangents. + Eigen::Matrix3d it_transform = transform.block<3, 3>(0, 0); + + it_transform = it_transform.inverse().transpose(); + + if (normal_att) { + TransformNormalizedAttribute(it_transform, normal_att); + } + if (tangent_att) { + TransformNormalizedAttribute(it_transform, tangent_att); + } + } +} + +namespace { + +// Merges entries from |src_metadata| to |dst_metadata|. Any metadata entries +// with the same names are left unchanged. +void MergeMetadataInternal(const Metadata &src_metadata, + Metadata *dst_metadata) { + const auto &src_entries = src_metadata.entries(); + const auto &dst_entries = dst_metadata->entries(); + for (const auto &it : src_entries) { + if (dst_entries.find(it.first) != dst_entries.end()) { + // Source entry already exists in the target metadata. + continue; + } + // Copy over the entry (entries don't store the data type so binary copy + // is ok). + dst_metadata->AddEntryBinary(it.first, it.second.data()); + } + + // Merge any sub-metadata. + const auto &src_sub_metadata = src_metadata.sub_metadatas(); + const auto &dst_sub_metadata = dst_metadata->sub_metadatas(); + for (const auto &it : src_sub_metadata) { + if (dst_sub_metadata.find(it.first) == dst_sub_metadata.end()) { + // Source sub-metadata doesn't exists in the target metadata, copy it + // over. + std::unique_ptr sub_metadata(new Metadata(*it.second)); + dst_metadata->AddSubMetadata(it.first, std::move(sub_metadata)); + continue; + } + // Merge entries on the sub-metadata. + MergeMetadataInternal(*it.second, dst_metadata->sub_metadata(it.first)); + } +} + +} // namespace + +void MeshUtils::MergeMetadata(const Mesh &src_mesh, Mesh *dst_mesh) { + const auto *src_metadata = src_mesh.GetMetadata(); + if (src_metadata == nullptr) { + return; // Nothing to merge. + } + if (dst_mesh->GetMetadata() == nullptr) { + // Create new metadata for the |dst_mesh|. We do not copy the metadata + // directly because some of the underlying attribute metadata may need to + // be remapped to the format used by |dst_mesh| (e.g. unique ids of the + // attributes may have changed or some attributes may be missing on the + // |dst_mesh|). + std::unique_ptr new_metadata(new GeometryMetadata()); + dst_mesh->AddMetadata(std::move(new_metadata)); + } + auto *dst_metadata = dst_mesh->metadata(); + + // First go over all entries of the geometry part of |src_metadata|. + MergeMetadataInternal(*src_metadata, dst_metadata); + + // Go over attribute metadata. Merges only metadata for attributes that exist + // both on the source and target meshes. Attribute unique ids are remapped + // if needed. + for (int att_type_i = 0; + att_type_i < GeometryAttribute::NAMED_ATTRIBUTES_COUNT; ++att_type_i) { + const GeometryAttribute::Type att_type = + static_cast(att_type_i); + // TODO(ostava): Handle case when the number of attributes of a given type + // does not match. + if (src_mesh.NumNamedAttributes(att_type) != + dst_mesh->NumNamedAttributes(att_type)) { + continue; + } + for (int j = 0; j < src_mesh.NumNamedAttributes(att_type); ++j) { + // First check if we have a metadata for this attribute. + const PointAttribute *const src_att = + src_mesh.GetNamedAttribute(att_type, j); + const auto *src_metadata = + src_mesh.GetMetadata()->GetAttributeMetadataByUniqueId( + src_att->unique_id()); + if (src_metadata == nullptr) { + // No metadata at the source, ignore the attribute. + continue; + } + // Find target attribute corresponding to the source. + const PointAttribute *const dst_att = + dst_mesh->GetNamedAttribute(att_type, j); + if (dst_att == nullptr) { + // No corresponding attribute found, ignore the source metadata. + continue; + } + auto *dst_metadata = + dst_mesh->metadata()->attribute_metadata(dst_att->unique_id()); + if (dst_metadata == nullptr) { + // Copy over the metadata (with remapped attribute unique id). + std::unique_ptr new_metadata( + new AttributeMetadata(*src_metadata)); + new_metadata->set_att_unique_id(dst_att->unique_id()); + dst_mesh->metadata()->AddAttributeMetadata(std::move(new_metadata)); + continue; + } + // Merge metadata entries. + MergeMetadataInternal(*src_metadata, dst_metadata); + } + } +} + +Status MeshUtils::RemoveUnusedMeshFeatures(Mesh *mesh) { + // Unused mesh features are features that are not used by any face / vertex + // of the |mesh|. Currently, each mesh feature can be "masked" for specific + // materials, in which case we need to check whether the mask materials + // are present in the |mesh|. If not, we can remove the mesh features from the + // mesh. + const PointAttribute *const mat_att = + mesh->GetNamedAttribute(GeometryAttribute::MATERIAL); + // Find which materials are used. + std::unordered_set used_materials; + if (mat_att == nullptr) { + // Only material with index 0 is assumed to be used. + used_materials.insert(0); + } else { + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + used_materials.insert(mat_index); + } + } + + std::vector unused_mesh_features; + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + bool is_used = false; + if (mesh->NumMeshFeaturesMaterialMasks(mfi) == 0) { + is_used = true; + } else { + for (int mask_i = 0; mask_i < mesh->NumMeshFeaturesMaterialMasks(mfi); + ++mask_i) { + const int material_index = + mesh->GetMeshFeaturesMaterialMask(mfi, mask_i); + if (used_materials.count(material_index)) { + is_used = true; + break; + } + } + } + if (!is_used) { + unused_mesh_features.push_back(mfi); + } + } + + // Remove the unused mesh features (from back). + for (auto it = unused_mesh_features.rbegin(); + it != unused_mesh_features.rend(); ++it) { + const MeshFeaturesIndex mfi = *it; + mesh->RemoveMeshFeatures(mfi); + } + + // Remove all features textures that are not used anymore. + + // First find which textures are referenced by the mesh features. + std::unordered_set used_textures; + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + const Texture *const texture = + mesh->GetMeshFeatures(mfi).GetTextureMap().texture(); + if (texture) { + used_textures.insert(texture); + } + } + + if (!used_textures.empty() && + mesh->GetNonMaterialTextureLibrary().NumTextures() == 0) { + return ErrorStatus( + "Trying to remove mesh features textures that are not owned by the " + "mesh."); + } + + // Remove all unreferenced textures from the non-material texture library. + for (int ti = mesh->GetNonMaterialTextureLibrary().NumTextures() - 1; ti >= 0; + --ti) { + const Texture *const texture = + mesh->GetNonMaterialTextureLibrary().GetTexture(ti); + if (used_textures.count(texture) == 0) { + mesh->GetNonMaterialTextureLibrary().RemoveTexture(ti); + } + } + return OkStatus(); +} + +bool MeshUtils::FlipTextureUvValues(bool flip_u, bool flip_v, + PointAttribute *att) { + if (att->attribute_type() != GeometryAttribute::TEX_COORD) { + return false; + } + if (att->data_type() != DataType::DT_FLOAT32) { + return false; + } + if (att->num_components() != 2) { + return false; + } + + std::array value; + for (AttributeValueIndex avi(0); avi < att->size(); ++avi) { + if (!att->GetValue(avi, &value)) { + return false; + } + if (flip_u) { + value[0] = 1.0 - value[0]; + } + if (flip_v) { + value[1] = 1.0 - value[1]; + } + att->SetAttributeValue(avi, value.data()); + } + return true; +} + +// TODO(fgalligan): Change att_id to be of type const PointAttribute &. +int MeshUtils::CountDegenerateFaces(const Mesh &mesh, int att_id) { + const PointAttribute *const att = mesh.attribute(att_id); + if (att == nullptr) { + return -1; + } + const int num_components = att->num_components(); + switch (num_components) { + case 2: + return MeshUtils::CountDegenerateFaces(mesh, *att); + case 3: + return MeshUtils::CountDegenerateFaces(mesh, *att); + case 4: + return MeshUtils::CountDegenerateFaces(mesh, *att); + default: + break; + } + return -1; +} + +StatusOr MeshUtils::FindLowestTextureQuantization( + const Mesh &mesh, const PointAttribute &pos_att, int pos_quantization_bits, + const PointAttribute &tex_att, int tex_target_quantization_bits) { + if (tex_target_quantization_bits < 0 || tex_target_quantization_bits >= 30) { + return Status(Status::DRACO_ERROR, + "Target texture quantization is out of range."); + } + // The target quantization is no quantization, so return 0. + if (tex_target_quantization_bits == 0) { + return 0; + } + const uint32_t pos_max_quantized_value = (1 << (pos_quantization_bits)) - 1; + AttributeQuantizationTransform pos_transform; + if (!pos_transform.ComputeParameters(pos_att, pos_quantization_bits)) { + return Status(Status::DRACO_ERROR, + "Failed computing position quantization parameters."); + } + + // Get all degenerate faces for positions. If the model already has + // degenerate faces for positions, but valid faces for texture coordinates, + // those will not count as new degenerate faces for texture coordinates, + // because the faces would not have been rendered anyway. + const std::vector pos_degenerate_faces_sorted = + MeshUtils::ListDegenerateQuantizedFaces( + mesh, pos_att, pos_transform.range(), pos_max_quantized_value, false); + + // Initialize return value to zero signifying that it could not find a + // quantization that did not cause any new degenerate faces. + int lowest_quantization_bits = 0; + int min_quantization_bits = tex_target_quantization_bits; + int max_quantization_bits = 29; + while (true) { + const int curr_quantization_bits = + min_quantization_bits + + (max_quantization_bits - min_quantization_bits) / 2; + AttributeQuantizationTransform transform; + if (!transform.ComputeParameters(tex_att, curr_quantization_bits)) { + return Status(Status::DRACO_ERROR, + "Failed computing texture quantization parameters."); + } + + const uint32_t max_quantized_value = (1 << (curr_quantization_bits)) - 1; + + // Get only new degenerate faces for texture coordinates. If the model + // already has degenerate faces for texture coordinates, we don't want to + // take into account those faces in the source, because those faces would + // not have been rendered correctly anyway. + const std::vector tex_degenerate_faces_sorted = + MeshUtils::ListDegenerateQuantizedFaces( + mesh, tex_att, transform.range(), max_quantized_value, true); + + if (tex_degenerate_faces_sorted.size() <= + pos_degenerate_faces_sorted.size()) { + if (std::includes(pos_degenerate_faces_sorted.begin(), + pos_degenerate_faces_sorted.end(), + tex_degenerate_faces_sorted.begin(), + tex_degenerate_faces_sorted.end())) { + // Degenerate texture coordinate faces are a subset of position + // degenerate faces. + lowest_quantization_bits = curr_quantization_bits; + } + } + + if (lowest_quantization_bits == curr_quantization_bits) { + // The lowest quantization is the current quantization, see if lower + // quantization is possible. + max_quantization_bits = curr_quantization_bits - 1; + } else { + min_quantization_bits = curr_quantization_bits + 1; + } + if (min_quantization_bits > max_quantization_bits) { + break; + } + } + return lowest_quantization_bits; +} + +void MeshUtils::TransformNormalizedAttribute(const Eigen::Matrix3d &transform, + PointAttribute *att) { + for (AttributeValueIndex avi(0); avi < att->size(); ++avi) { + // Store up to 4 component values. + Vector4f val(0, 0, 0, 1); + att->GetValue(avi, &val); + // Ignore the last component during transformation. + Eigen::Vector3d transformed_val(val[0], val[1], val[2]); + transformed_val = transform * transformed_val; + transformed_val = transformed_val.normalized(); + // Last component is passed to the transformed value. + val = Vector4f(transformed_val[0], transformed_val[1], transformed_val[2], + val[3]); + + // Set the value to the attribute. Note that in case the attribute is using + // fewer than 4 components, the 4th component is going to be ignored. + att->SetAttributeValue(avi, &val[0]); + } +} + +template +int MeshUtils::CountDegenerateFaces(const Mesh &mesh, + const PointAttribute &att) { + if (att.data_type() != DataType::DT_FLOAT32) { + return -1; + } + std::array values; + int degenerate_values = 0; + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto &face = mesh.face(fi); + for (int c = 0; c < 3; ++c) { + att.GetMappedValue(face[c], &values[c][0]); + } + if (values[0] == values[1] || values[0] == values[2] || + values[1] == values[2]) { + degenerate_values++; + } + } + return degenerate_values; +} + +std::vector MeshUtils::ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only) { + const int num_components = att.num_components(); + switch (num_components) { + case 2: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + case 3: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + case 4: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + default: + break; + } + return std::vector(); +} + +template +std::vector MeshUtils::ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only) { + std::array values; + std::array quantized_values; + + Quantizer quantizer; + quantizer.Init(range, max_quantized_value); + std::vector degenerate_faces; + + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto &face = mesh.face(fi); + for (int c = 0; c < 3; ++c) { + att.GetMappedValue(face[c], &values[c][0]); + for (int i = 0; i < att_components_t::dimension; ++i) { + quantized_values[c][i] = quantizer.QuantizeFloat(values[c][i]); + } + } + + if (quantized_degenerate_only && + (values[0] == values[1] || values[0] == values[2] || + values[1] == values[2])) { + continue; + } + if (quantized_values[0] == quantized_values[1] || + quantized_values[0] == quantized_values[2] || + quantized_values[1] == quantized_values[2]) { + degenerate_faces.push_back(fi); + } + } + return degenerate_faces; +} + +bool MeshUtils::HasAutoGeneratedTangents(const Mesh &mesh) { + const int tangent_att_id = + mesh.GetNamedAttributeId(draco::GeometryAttribute::TANGENT); + if (tangent_att_id == -1) { + return false; + } + const auto metadata = mesh.GetAttributeMetadataByAttributeId(tangent_att_id); + if (metadata) { + int is_auto_generated = 0; + if (metadata->GetEntryInt("auto_generated", &is_auto_generated) && + is_auto_generated == 1) { + return true; + } + } + return false; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.h new file mode 100644 index 000000000..e17dfd8ed --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils.h @@ -0,0 +1,102 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_UTILS_H_ +#define DRACO_MESH_MESH_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/core/status_or.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Helper class containing various utilities operating on draco::Mesh. +// TODO(ostava): Move scattered functions in this folder here (e.g. corner table +// construction). +class MeshUtils { + public: + // Transforms |mesh| using the |transform| matrix. The mesh is transformed + // in-place. + static void TransformMesh(const Eigen::Matrix4d &transform, Mesh *mesh); + + // Merges metadata from |src_mesh| to |dst_mesh|. Any metadata with the same + // names are left unchanged. + static void MergeMetadata(const Mesh &src_mesh, Mesh *dst_mesh); + + // Removes unused MeshFeatures from |mesh|. If the |mesh| contains any mesh + // feature textures, the textures must be owned by the |mesh| otherwise an + // error is returned. + static Status RemoveUnusedMeshFeatures(Mesh *mesh); + + // Flips the UV values of |att|. + static bool FlipTextureUvValues(bool flip_u, bool flip_v, + PointAttribute *att); + + // Counts the number of degenerate faces in |mesh| for attribute |att_id|. + // Returns < 0 if counting of degenerate faces is not supported for |att_id|. + static int CountDegenerateFaces(const Mesh &mesh, int att_id); + + // Searches for the lowest texture quantization bits for |tex_att| that does + // not introduce any new texture coordinate degenerate faces. The range for + // the search is |tex_target_quantization_bits| - 29, inclusive. The function + // does not count texture coordinate degenerate faces already in the source. + // Nor does it count any new texture coordinate degenerate faces that are a + // subset of new position degenerate faces created from the quantization of + // |pos_att| using |pos_quantization_bits|. Returns the lowest quantization + // bits within the specified range or zero signifying that it could not find a + // quantization that did not cause any new degenerate faces. + static StatusOr FindLowestTextureQuantization( + const Mesh &mesh, const PointAttribute &pos_att, + int pos_quantization_bits, const PointAttribute &tex_att, + int tex_target_quantization_bits); + + // Helper function that checks whether a mesh has auto-generated tangents. + // See go/tangents_and_draco_simplifier. + static bool HasAutoGeneratedTangents(const Mesh &mesh); + + private: + static void TransformNormalizedAttribute(const Eigen::Matrix3d &transform, + PointAttribute *att); + + template + static int CountDegenerateFaces(const Mesh &mesh, const PointAttribute &att); + + // Returns a sorted list of degenerate faces for |att|. |att| must use |mesh| + // for its connectivity. |range| and |max_quantized_value| are the values + // passed into the quantizer. |quantized_degenerate_only|, is true will only + // include degenerate faces caused by the quantization. Otherwise all + // degenerate faces will be included, those made by the quantization and those + // already in the source. + static std::vector ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only); + + // Returns a sorted list of degenerate faces for |att|. |att_components_t| is + // the component count for |att| as a VectorD. E.g. Vector2f, Vector3f, or + // Vector4f. |quantized_components_t| is the quantized component count for + // |att| as a VectorD. E.g. VectorD, VectorD, or + // VectorD. + template + static std::vector ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils_test.cc new file mode 100644 index 000000000..022669cb0 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/mesh_utils_test.cc @@ -0,0 +1,391 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +// Compare normal vector rotated by |angle| around the x-axis. +void CompareRotatedNormals(const draco::Mesh &mesh_0, const draco::Mesh &mesh_1, + float angle) { + const draco::PointAttribute *const norm_att_0 = + mesh_0.GetNamedAttribute(draco::GeometryAttribute::NORMAL); + const draco::PointAttribute *const norm_att_1 = + mesh_1.GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_EQ(norm_att_0->size(), norm_att_1->size()); + for (draco::AttributeValueIndex avi(0); avi < norm_att_0->size(); ++avi) { + Eigen::Vector3f norm_0, norm_1; + norm_att_0->GetValue(avi, norm_0.data()); + norm_att_1->GetValue(avi, norm_1.data()); + + // Project the normals into yz plane + norm_0[0] = 0.f; + norm_1[0] = 0.f; + + if (norm_0.squaredNorm() < 1e-6f) { + // Normal pointing towards X. Make sure the rotated normal is about the + // same. + ASSERT_NEAR(norm_1.squaredNorm(), 0.f, 1e-6f); + continue; + } + + // Ensure the angle between the normals is as expected. + norm_0.normalize(); + norm_1.normalize(); + const float norm_angle = + std::atan2(norm_0.cross(norm_1).norm(), norm_0.dot(norm_1)); + ASSERT_NEAR(std::abs(norm_angle), angle, 1e-6f); + } +} + +TEST(MeshUtilsTest, TestTransform) { + auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + draco::Mesh transformed_mesh; + transformed_mesh.Copy(*mesh); + Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + + // Rotate the mesh by 45 deg around the x-axis. + transform.block<3, 3>(0, 0) = + Eigen::Quaterniond( + Eigen::AngleAxisd(M_PI / 4.f, Eigen::Vector3d::UnitX())) + .normalized() + .toRotationMatrix(); + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + CompareRotatedNormals(*mesh, transformed_mesh, M_PI / 4.f); + + // Now rotate the cube back. + transform.block<3, 3>(0, 0) = + Eigen::Quaterniond( + Eigen::AngleAxisd(-M_PI / 4.f, Eigen::Vector3d::UnitX())) + .normalized() + .toRotationMatrix(); + + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + CompareRotatedNormals(*mesh, transformed_mesh, 0.f); +} + +TEST(MeshUtilsTest, TestTextureUvFlips) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Check that FlipTextureUvValues() only works on texture coordinates. + draco::PointAttribute *att = mesh->attribute(0); + ASSERT_EQ(att->attribute_type(), draco::GeometryAttribute::POSITION); + ASSERT_FALSE(draco::MeshUtils::FlipTextureUvValues(false, true, att)); + + att = mesh->attribute(1); + ASSERT_EQ(att->attribute_type(), draco::GeometryAttribute::TEX_COORD); + + // Get the values and flip the V values. + std::vector> check_uv_values; + check_uv_values.resize(att->size()); + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &check_uv_values[avi.value()]); + check_uv_values[avi.value()][1] = 1.0 - check_uv_values[avi.value()][1]; + } + + ASSERT_TRUE(draco::MeshUtils::FlipTextureUvValues(false, true, att)); + + std::array value; + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &value); + ASSERT_EQ(value[0], check_uv_values[avi.value()][0]); + ASSERT_EQ(value[1], check_uv_values[avi.value()][1]); + } + + // Flip the U values. + for (int i = 0; i < check_uv_values.size(); ++i) { + check_uv_values[i][0] = 1.0 - check_uv_values[i][0]; + } + + ASSERT_TRUE(draco::MeshUtils::FlipTextureUvValues(true, false, att)); + + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &value); + ASSERT_EQ(value[0], check_uv_values[avi.value()][0]); + ASSERT_EQ(value[1], check_uv_values[avi.value()][1]); + } +} + +// Tests counting degenerate values for positions and texture coordinates for +// both scene and mesh. +TEST(MeshUtilsTest, CountDegenerateValuesLantern) { + int degenerate_positions_scene = 0; + int degenerate_tex_coords_scene = 0; + std::unique_ptr scene = + draco::ReadSceneFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(scene, nullptr); + + for (int mgi = 0; mgi < scene->NumMeshGroups(); ++mgi) { + const draco::MeshGroup *const mesh_group = + scene->GetMeshGroup(draco::MeshGroupIndex(mgi)); + ASSERT_NE(mesh_group, nullptr); + + for (int mi = 0; mi < mesh_group->NumMeshInstances(); ++mi) { + const draco::MeshIndex mesh_index = + mesh_group->GetMeshInstance(mi).mesh_index; + const draco::Mesh &m = scene->GetMesh(mesh_index); + + for (int i = 0; i < m.num_attributes(); ++i) { + const draco::PointAttribute *const att = m.attribute(i); + ASSERT_NE(att, nullptr); + + if (att->attribute_type() == draco::GeometryAttribute::Type::POSITION) { + degenerate_positions_scene += + draco::MeshUtils::CountDegenerateFaces(m, i); + } else if (att->attribute_type() == + draco::GeometryAttribute::Type::TEX_COORD) { + degenerate_tex_coords_scene += + draco::MeshUtils::CountDegenerateFaces(m, i); + } + } + } + } + EXPECT_EQ(degenerate_positions_scene, 0); + EXPECT_EQ(degenerate_tex_coords_scene, 2); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(mesh, nullptr); + for (int i = 0; i < mesh->num_attributes(); ++i) { + const draco::PointAttribute *const att = mesh->attribute(i); + ASSERT_NE(att, nullptr); + if (att->attribute_type() == draco::GeometryAttribute::Type::POSITION) { + EXPECT_EQ(draco::MeshUtils::CountDegenerateFaces(*mesh, i), + degenerate_positions_scene); + } else if (att->attribute_type() == + draco::GeometryAttribute::Type::TEX_COORD) { + EXPECT_EQ(draco::MeshUtils::CountDegenerateFaces(*mesh, i), + degenerate_tex_coords_scene); + } + } +} + +// Tests finding the lowest quantization bits for the texture coordinate in a +// mesh. +TEST(MeshUtilsTest, FindLowsetTextureQuantizationLanternMesh) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(mesh, nullptr); + + const int pos_quantization_bits = 11; + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::Type::POSITION, 0); + ASSERT_NE(pos_att, nullptr); + + const draco::PointAttribute *const tex_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::Type::TEX_COORD, 0); + ASSERT_NE(tex_att, nullptr); + + // Tests target no quantization returns no quantization. + const int target_no_quantization_bits = 0; + DRACO_ASSIGN_OR_ASSERT(const int no_quantization_bits, + draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, + target_no_quantization_bits)); + ASSERT_EQ(no_quantization_bits, 0); + + // Test failures. + const int out_of_range_low = -1; + const auto statusor_low = draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, out_of_range_low); + ASSERT_FALSE(statusor_low.ok()); + + const int out_of_range_high = 30; + const auto statusor_high = draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, out_of_range_high); + ASSERT_FALSE(statusor_high.ok()); + + // Tests finding the lowest quantization bits for the texture coordinate. + const int target_bits = 6; + DRACO_ASSIGN_OR_ASSERT( + const int lowest_bits, + draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, target_bits)); + ASSERT_EQ(lowest_bits, 14); +} + +// Tests finding the lowest quantization bits for the texture coordinates for +// the three meshes in the scene. +TEST(MeshUtilsTest, FindLowsetTextureQuantizationLanternScene) { + std::unique_ptr scene = + draco::ReadSceneFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(scene, nullptr); + + const std::vector expected_mesh_quantization_bits{11, 8, 14}; + for (int mi = 0; mi < scene->NumMeshes(); ++mi) { + const draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(mi)); + + const int pos_quantization_bits = 11; + const draco::PointAttribute *const pos_att = + mesh.GetNamedAttribute(draco::GeometryAttribute::Type::POSITION, 0); + ASSERT_NE(pos_att, nullptr); + + const draco::PointAttribute *const tex_att = + mesh.GetNamedAttribute(draco::GeometryAttribute::Type::TEX_COORD, 0); + ASSERT_NE(tex_att, nullptr); + + const int target_bits = 8; + DRACO_ASSIGN_OR_ASSERT( + const int lowest_bits, + draco::MeshUtils::FindLowestTextureQuantization( + mesh, *pos_att, pos_quantization_bits, *tex_att, target_bits)); + ASSERT_EQ(lowest_bits, expected_mesh_quantization_bits[mi]); + } +} + +TEST(MeshUtilsTest, CheckAutoGeneratedTangents) { + // Test verifies that MeshUtils::HasAutoGeneratedTangents works as intended. + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("sphere_no_tangents.gltf"); + ASSERT_NE(mesh, nullptr); + + ASSERT_TRUE(draco::MeshUtils::HasAutoGeneratedTangents(*mesh)); +} + +TEST(MeshUtilsTest, CheckMergeMetadata) { + // Test verifies that we can merge metadata using MeshUtils::MergeMetadata(). + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("sphere_no_tangents.gltf"); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr other_mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + + ASSERT_NE(mesh->GetMetadata(), nullptr); + // One attribute metadata (for the tangent attribute) and no other entries. + ASSERT_EQ(mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(mesh->GetMetadata()->num_entries(), 0); + + // No metadata at the other attribute. + ASSERT_EQ(other_mesh->GetMetadata(), nullptr); + + // First try to merge |other_mesh| metadata to |mesh|. This shouldn't do + // anything. + draco::MeshUtils::MergeMetadata(*other_mesh, mesh.get()); + ASSERT_EQ(mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(mesh->GetMetadata()->num_entries(), 0); + + // Merge |mesh| metadata to |other_mesh|. This will create empty metadata but + // not any attribute metadata because |other_mesh| doesn't have the tangent + // attribute. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 0); + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 0); + ASSERT_FALSE(draco::MeshUtils::HasAutoGeneratedTangents(*other_mesh)); + + // Add dummy tangent attribute to the |other_mesh|. + std::unique_ptr tang_att(new draco::PointAttribute()); + draco::PointAttribute *const tang_att_ptr = tang_att.get(); + tang_att->set_attribute_type(draco::GeometryAttribute::TANGENT); + other_mesh->AddAttribute(std::move(tang_att)); + + // Merge |mesh| metadata to |other_mesh|. This time the tangent metadata + // should be copied over. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 0); + ASSERT_NE(other_mesh->GetMetadata()->GetAttributeMetadataByUniqueId( + tang_att_ptr->unique_id()), + nullptr); + ASSERT_TRUE(draco::MeshUtils::HasAutoGeneratedTangents(*other_mesh)); + + // Now add some entries to the geometry metadata and merge again. + mesh->metadata()->AddEntryInt("test_int_0", 0); + mesh->metadata()->AddEntryInt("test_int_1", 1); + mesh->metadata()->AddEntryInt("test_int_shared", 2); + other_mesh->metadata()->AddEntryInt("test_int_shared", 3); + + // "test_int_0" and "test_int_1" should be copied over while + // "test_entry_shared" should stay unchanged. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + // Attribute metadata should stay unchanged. + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_NE(other_mesh->GetMetadata()->GetAttributeMetadataByUniqueId( + tang_att_ptr->unique_id()), + nullptr); + ASSERT_EQ(other_mesh->GetMetadata() + ->GetAttributeMetadataByUniqueId(tang_att_ptr->unique_id()) + ->num_entries(), + 1); + + // Check the geometry metadata entries. + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 3); + int metadata_value; + ASSERT_TRUE( + other_mesh->GetMetadata()->GetEntryInt("test_int_0", &metadata_value)); + ASSERT_EQ(metadata_value, 0); + ASSERT_TRUE( + other_mesh->GetMetadata()->GetEntryInt("test_int_1", &metadata_value)); + ASSERT_EQ(metadata_value, 1); + + // The shared entry should have an unchanged value. + ASSERT_TRUE(other_mesh->GetMetadata()->GetEntryInt("test_int_shared", + &metadata_value)); + ASSERT_EQ(metadata_value, 3); +} + +TEST(MeshUtilsTest, RemoveUnusedMeshFeatures) { + // Test verifies that MeshUtils::RemoveUnusedMeshFeatures works as intended. + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf"); + ASSERT_NE(mesh, nullptr); + + // The input mesh should have five mesh features and two features textures. + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // All of those features and textures should be used so calling the method + // below shouldn't do anything. + draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get()); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Now remove material 1 that is mapped to first two mesh features. + draco::PointAttribute *mat_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL)); + + // This basically remaps all faces from material 1 to material 0. + uint32_t mat_index = 0; + mat_att->SetAttributeValue(draco::AttributeValueIndex(1), &mat_index); + + // Try to remove the mesh features again. + draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get()); + + // Three of the mesh features should have been removed as well as one mesh + // features texture. + ASSERT_EQ(mesh->NumMeshFeatures(), 2); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 1); + + // Ensure the remaining mesh features are mapped to the correct material. + for (draco::MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + ASSERT_EQ(mesh->NumMeshFeaturesMaterialMasks(mfi), 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(mfi, 0), 0); + } +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc index 60b0c50b8..2af94a052 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc @@ -23,11 +23,23 @@ void TriangleSoupMeshBuilder::Start(int num_faces) { attribute_element_types_.clear(); } +#ifdef DRACO_TRANSCODER_SUPPORTED +void TriangleSoupMeshBuilder::SetName(const std::string &name) { + mesh_->SetName(name); +} +#endif // DRACO_TRANSCODER_SUPPORTED + int TriangleSoupMeshBuilder::AddAttribute( GeometryAttribute::Type attribute_type, int8_t num_components, DataType data_type) { + return AddAttribute(attribute_type, num_components, data_type, false); +} + +int TriangleSoupMeshBuilder::AddAttribute( + GeometryAttribute::Type attribute_type, int8_t num_components, + DataType data_type, bool normalized) { GeometryAttribute va; - va.Init(attribute_type, nullptr, num_components, data_type, false, + va.Init(attribute_type, nullptr, num_components, data_type, normalized, DataTypeLength(data_type) * num_components, 0); attribute_element_types_.push_back(-1); return mesh_->AddAttribute(va, true, mesh_->num_points()); @@ -41,8 +53,6 @@ void TriangleSoupMeshBuilder::SetAttributeValuesForFace( att->SetAttributeValue(AttributeValueIndex(start_index), corner_value_0); att->SetAttributeValue(AttributeValueIndex(start_index + 1), corner_value_1); att->SetAttributeValue(AttributeValueIndex(start_index + 2), corner_value_2); - // TODO(ostava): The below code should be called only for one attribute. - // It will work OK even for multiple attributes, but it's redundant. mesh_->SetFace(face_id, {{PointIndex(start_index), PointIndex(start_index + 1), PointIndex(start_index + 2)}}); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h index 89466e1d8..503fe84c5 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h @@ -15,7 +15,14 @@ #ifndef DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ #define DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ +#include +#include + #include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#endif #include "draco/mesh/mesh.h" namespace draco { @@ -25,15 +32,25 @@ namespace draco { // deduplicated. class TriangleSoupMeshBuilder { public: + // Index type of the inserted element. + typedef FaceIndex ElementIndex; + // Starts mesh building for a given number of faces. // TODO(ostava): Currently it's necessary to select the correct number of // faces upfront. This should be generalized, but it will require us to // rewrite our attribute resizing functions. void Start(int num_faces); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Sets mesh name. + void SetName(const std::string &name); +#endif // DRACO_TRANSCODER_SUPPORTED + // Adds an empty attribute to the mesh. Returns the new attribute's id. int AddAttribute(GeometryAttribute::Type attribute_type, int8_t num_components, DataType data_type); + int AddAttribute(GeometryAttribute::Type attribute_type, + int8_t num_components, DataType data_type, bool normalized); // Sets values for a given attribute on all corners of a given face. void SetAttributeValuesForFace(int att_id, FaceIndex face_id, @@ -41,12 +58,34 @@ class TriangleSoupMeshBuilder { const void *corner_value_1, const void *corner_value_2); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Converts input values of type T into internal representation used by + // |att_id|. Each input value needs to have |input_num_components| entries. + template + Status ConvertAndSetAttributeValuesForFace(int att_id, FaceIndex face_id, + int input_num_components, + const T *corner_value_0, + const T *corner_value_1, + const T *corner_value_2); +#endif + // Sets value for a per-face attribute. If all faces of a given attribute are // set with this method, the attribute will be marked as per-face, otherwise // it will be marked as per-corner attribute. void SetPerFaceAttributeValueForFace(int att_id, FaceIndex face_id, const void *value); + // Add metadata. + void AddMetadata(std::unique_ptr metadata) { + mesh_->AddMetadata(std::move(metadata)); + } + + // Add metadata for an attribute. + void AddAttributeMetadata(int32_t att_id, + std::unique_ptr metadata) { + mesh_->AddAttributeMetadata(att_id, std::move(metadata)); + } + // Finalizes the mesh or returns nullptr on error. // Once this function is called, the builder becomes invalid and cannot be // used until the method Start() is called again. @@ -58,6 +97,30 @@ class TriangleSoupMeshBuilder { std::unique_ptr mesh_; }; +#ifdef DRACO_TRANSCODER_SUPPORTED +template +Status TriangleSoupMeshBuilder::ConvertAndSetAttributeValuesForFace( + int att_id, FaceIndex face_id, int input_num_components, + const T *corner_value_0, const T *corner_value_1, const T *corner_value_2) { + const int start_index = 3 * face_id.value(); + PointAttribute *const att = mesh_->attribute(att_id); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 0), + input_num_components, corner_value_0)); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 1), + input_num_components, corner_value_1)); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 2), + input_num_components, corner_value_2)); + mesh_->SetFace(face_id, + {{PointIndex(start_index), PointIndex(start_index + 1), + PointIndex(start_index + 2)}}); + attribute_element_types_[att_id] = MESH_CORNER_ATTRIBUTE; + return OkStatus(); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco #endif // DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc index 171f8fe24..b23641760 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc @@ -14,7 +14,11 @@ // #include "draco/mesh/triangle_soup_mesh_builder.h" +#include +#include + #include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" namespace draco { @@ -26,6 +30,9 @@ TEST_F(TriangleSoupMeshBuilderTest, CubeTest) { // of the provided triangle soup data. TriangleSoupMeshBuilder mb; mb.Start(12); +#ifdef DRACO_TRANSCODER_SUPPORTED + mb.SetName("Cube"); +#endif const int pos_att_id = mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); // clang-format off @@ -92,6 +99,9 @@ TEST_F(TriangleSoupMeshBuilderTest, CubeTest) { std::unique_ptr mesh = mb.Finalize(); ASSERT_NE(mesh, nullptr) << "Failed to build the cube mesh."; +#ifdef DRACO_TRANSCODER_SUPPORTED + EXPECT_EQ(mesh->GetName(), "Cube"); +#endif EXPECT_EQ(mesh->num_points(), 8) << "Unexpected number of vertices."; EXPECT_EQ(mesh->num_faces(), 12) << "Unexpected number of faces."; } @@ -139,7 +149,7 @@ TEST_F(TriangleSoupMeshBuilderTest, TestPerFaceAttribs) { Vector3f(0.f, 1.f, 0.f).data(), Vector3f(1.f, 1.f, 0.f).data(), Vector3f(0.f, 1.f, 1.f).data()); - mb.SetPerFaceAttributeValueForFace(gen_att_id, FaceIndex(4), &bool_false);; + mb.SetPerFaceAttributeValueForFace(gen_att_id, FaceIndex(4), &bool_false); mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(5), Vector3f(0.f, 1.f, 1.f).data(), @@ -189,9 +199,69 @@ TEST_F(TriangleSoupMeshBuilderTest, TestPerFaceAttribs) { std::unique_ptr mesh = mb.Finalize(); ASSERT_NE(mesh, nullptr) << "Failed to build the cube mesh."; +#ifdef DRACO_TRANSCODER_SUPPORTED + EXPECT_TRUE(mesh->GetName().empty()); +#endif EXPECT_EQ(mesh->num_faces(), 12) << "Unexpected number of faces."; EXPECT_EQ(mesh->GetAttributeElementType(gen_att_id), MESH_FACE_ATTRIBUTE) << "Unexpected attribute element type."; } +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(TriangleSoupMeshBuilderTest, NormalizedColor) { + // This tests, verifies that the mesh builder constructs a valid model with + // normalized integer colors using floating points as input. + TriangleSoupMeshBuilder mb; + mb.Start(2); + const int pos_att_id = + mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); + const int color_att_id = + mb.AddAttribute(GeometryAttribute::COLOR, 3, DT_UINT8, true); + + mb.SetAttributeValuesForFace( + pos_att_id, FaceIndex(0), Vector3f(0.f, 0.f, 0.f).data(), + Vector3f(1.f, 0.f, 0.f).data(), Vector3f(0.f, 1.f, 0.f).data()); + DRACO_ASSERT_OK(mb.ConvertAndSetAttributeValuesForFace( + color_att_id, FaceIndex(0), 4, Vector4f(0.f, 0.f, 0.f, 1.f).data(), + Vector4f(1.f, 1.f, 1.f, 1.f).data(), + Vector4f(0.5f, 0.5f, 0.5f, 1.f).data())); + mb.SetAttributeValuesForFace( + pos_att_id, FaceIndex(1), Vector3f(0.f, 1.f, 0.f).data(), + Vector3f(1.f, 0.f, 0.f).data(), Vector3f(1.f, 1.f, 0.f).data()); + + DRACO_ASSERT_OK(mb.ConvertAndSetAttributeValuesForFace( + color_att_id, FaceIndex(1), 4, Vector4f(0.5f, 0.5f, 0.5f, 1.f).data(), + Vector4f(1.f, 1.f, 1.f, 1.f).data(), + Vector4f(0.25f, 0.0f, 1.f, 1.f).data())); + + std::unique_ptr mesh = mb.Finalize(); + ASSERT_NE(mesh, nullptr) << "Failed to build the test mesh."; + + EXPECT_EQ(mesh->num_points(), 4) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 2) << "Unexpected number of faces."; + + const auto *col_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::COLOR); + ASSERT_NE(col_att, nullptr) << "Missing color attribute."; + ASSERT_EQ(col_att->size(), 4); + + // All colors should be in range 0-255. + uint8_t max_val = 0, min_val = 255; + for (draco::AttributeValueIndex avi(0); avi < col_att->size(); ++avi) { + VectorD cval; + col_att->GetValue(avi, &cval); + const uint8_t max = cval.MaxCoeff(); + const uint8_t min = cval.MinCoeff(); + if (max > max_val) { + max_val = max; + } + if (min < min_val) { + min_val = min; + } + } + ASSERT_EQ(max_val, 255); + ASSERT_EQ(min_val, 0); +} +#endif + } // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.cc index b83898140..b6a882c0b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.cc @@ -18,6 +18,19 @@ namespace draco { +AttributeMetadata::AttributeMetadata(const AttributeMetadata &metadata) + : Metadata(metadata) { + att_unique_id_ = metadata.att_unique_id_; +} + +GeometryMetadata::GeometryMetadata(const GeometryMetadata &metadata) + : Metadata(metadata) { + for (size_t i = 0; i < metadata.att_metadatas_.size(); ++i) { + att_metadatas_.push_back(std::unique_ptr( + new AttributeMetadata(*metadata.att_metadatas_[i]))); + } +} + const AttributeMetadata *GeometryMetadata::GetAttributeMetadataByStringEntry( const std::string &entry_name, const std::string &entry_value) const { for (auto &&att_metadata : att_metadatas_) { @@ -35,7 +48,7 @@ const AttributeMetadata *GeometryMetadata::GetAttributeMetadataByStringEntry( bool GeometryMetadata::AddAttributeMetadata( std::unique_ptr att_metadata) { - if (!att_metadata.get()) { + if (!att_metadata) { return false; } att_metadatas_.push_back(std::move(att_metadata)); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.h b/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.h index ec7ecb9ee..531bdef25 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/geometry_metadata.h @@ -25,6 +25,7 @@ namespace draco { class AttributeMetadata : public Metadata { public: AttributeMetadata() : att_unique_id_(0) {} + AttributeMetadata(const AttributeMetadata &metadata); explicit AttributeMetadata(const Metadata &metadata) : Metadata(metadata), att_unique_id_(0) {} @@ -57,6 +58,7 @@ struct AttributeMetadataHasher { class GeometryMetadata : public Metadata { public: GeometryMetadata() {} + GeometryMetadata(const GeometryMetadata &metadata); explicit GeometryMetadata(const Metadata &metadata) : Metadata(metadata) {} const AttributeMetadata *GetAttributeMetadataByStringEntry( diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.cc index 9141907ed..51b4e93a3 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.cc @@ -122,6 +122,14 @@ const Metadata *Metadata::GetSubMetadata(const std::string &name) const { return sub_ptr->second.get(); } +Metadata *Metadata::sub_metadata(const std::string &name) { + auto sub_ptr = sub_metadatas_.find(name); + if (sub_ptr == sub_metadatas_.end()) { + return nullptr; + } + return sub_ptr->second.get(); +} + void Metadata::RemoveEntry(const std::string &name) { // Actually just remove "name", no need to check if it exists. auto entry_ptr = entries_.find(name); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.h b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.h index 56d05e46a..12c1ba974 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata.h @@ -147,6 +147,7 @@ class Metadata { bool AddSubMetadata(const std::string &name, std::unique_ptr sub_metadata); const Metadata *GetSubMetadata(const std::string &name) const; + Metadata *sub_metadata(const std::string &name); void RemoveEntry(const std::string &name); diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_decoder.cc index a8e66f854..6468e3207 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_decoder.cc @@ -59,18 +59,25 @@ bool MetadataDecoder::DecodeGeometryMetadata(DecoderBuffer *in_buffer, } bool MetadataDecoder::DecodeMetadata(Metadata *metadata) { - struct MetadataPair { + // Limit metadata nesting depth to avoid stack overflow in destructor. + constexpr int kMaxSubmetadataLevel = 1000; + + struct MetadataTuple { Metadata *parent_metadata; Metadata *decoded_metadata; + int level; }; - std::vector metadata_stack; - metadata_stack.push_back({nullptr, metadata}); + std::vector metadata_stack; + metadata_stack.push_back({nullptr, metadata, 0}); while (!metadata_stack.empty()) { - const MetadataPair mp = metadata_stack.back(); + const MetadataTuple mp = metadata_stack.back(); metadata_stack.pop_back(); metadata = mp.decoded_metadata; if (mp.parent_metadata != nullptr) { + if (mp.level > kMaxSubmetadataLevel) { + return false; + } std::string sub_metadata_name; if (!DecodeName(&sub_metadata_name)) { return false; @@ -105,7 +112,8 @@ bool MetadataDecoder::DecodeMetadata(Metadata *metadata) { return false; } for (uint32_t i = 0; i < num_sub_metadata; ++i) { - metadata_stack.push_back({metadata, nullptr}); + metadata_stack.push_back( + {metadata, nullptr, mp.parent_metadata ? mp.level + 1 : mp.level}); } } return true; @@ -123,6 +131,9 @@ bool MetadataDecoder::DecodeEntry(Metadata *metadata) { if (data_size == 0) { return false; } + if (data_size > buffer_->remaining_size()) { + return false; + } std::vector entry_value(data_size); if (!buffer_->Decode(&entry_value[0], data_size)) { return false; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_test.cc index cf7ae6eee..03104e03e 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/metadata_test.cc @@ -104,12 +104,16 @@ TEST_F(MetadataTest, TestNestedMetadata) { sub_metadata->AddEntryInt("int", 100); metadata.AddSubMetadata("sub0", std::move(sub_metadata)); - const auto sub_metadata_ptr = metadata.GetSubMetadata("sub0"); + const auto sub_metadata_ptr = metadata.sub_metadata("sub0"); ASSERT_NE(sub_metadata_ptr, nullptr); int32_t int_value = 0; ASSERT_TRUE(sub_metadata_ptr->GetEntryInt("int", &int_value)); ASSERT_EQ(int_value, 100); + + sub_metadata_ptr->AddEntryInt("new_entry", 20); + ASSERT_TRUE(sub_metadata_ptr->GetEntryInt("new_entry", &int_value)); + ASSERT_EQ(int_value, 20); } TEST_F(MetadataTest, TestHardCopyMetadata) { diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.cc new file mode 100644 index 000000000..c6a5fd984 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.cc @@ -0,0 +1,183 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/property_table.h" + +#include +#include +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +bool PropertyTable::Schema::Object::operator==(const Object& other) const { + if (type_ != other.type_ || name_ != other.name_) { + return false; + } + switch (type_) { + case OBJECT: + if (objects_.size() != other.objects_.size()) { + return false; + } + for (int i = 0; i < objects_.size(); ++i) { + if (objects_[i] != other.objects_[i]) { + return false; + } + } + break; + case ARRAY: + if (array_.size() != other.array_.size()) { + return false; + } + for (int i = 0; i < array_.size(); ++i) { + if (array_[i] != other.array_[i]) { + return false; + } + } + break; + case STRING: + return string_ == other.string_; + case INTEGER: + return integer_ == other.integer_; + case BOOLEAN: + return boolean_ == other.boolean_; + } + return true; +} + +void PropertyTable::Schema::Object::Copy(const Object& src) { + name_ = src.name_; + type_ = src.type_; + objects_.reserve(src.objects_.size()); + for (const Object& obj : src.objects_) { + objects_.emplace_back(); + objects_.back().Copy(obj); + } + array_.reserve(src.array_.size()); + for (const Object& obj : src.array_) { + array_.emplace_back(); + array_.back().Copy(obj); + } + string_ = src.string_; + integer_ = src.integer_; + boolean_ = src.boolean_; +} + +PropertyTable::Property::Property() {} + +bool PropertyTable::Property::Data::operator==(const Data& other) const { + return data == other.data && target == other.target; +} + +bool PropertyTable::Property::Offsets::operator==(const Offsets& other) const { + return data == other.data && type == other.type; +} + +bool PropertyTable::Property::operator==(const Property& other) const { + return name_ == other.name_ && data_ == other.data_ && + array_offsets_ == other.array_offsets_ && + string_offsets_ == other.string_offsets_; +} + +void PropertyTable::Property::Copy(const Property& src) { + name_ = src.name_; + data_ = src.data_; + array_offsets_ = src.array_offsets_; + string_offsets_ = src.string_offsets_; +} + +void PropertyTable::Property::SetName(const std::string& name) { name_ = name; } +const std::string& PropertyTable::Property::GetName() const { return name_; } + +PropertyTable::Property::Data& PropertyTable::Property::GetData() { + return data_; +} +const PropertyTable::Property::Data& PropertyTable::Property::GetData() const { + return data_; +} + +const PropertyTable::Property::Offsets& +PropertyTable::Property::GetArrayOffsets() const { + return array_offsets_; +} +PropertyTable::Property::Offsets& PropertyTable::Property::GetArrayOffsets() { + return array_offsets_; +} + +const PropertyTable::Property::Offsets& +PropertyTable::Property::GetStringOffsets() const { + return string_offsets_; +} +PropertyTable::Property::Offsets& PropertyTable::Property::GetStringOffsets() { + return string_offsets_; +} + +PropertyTable::PropertyTable() : count_(0) {} + +bool PropertyTable::operator==(const PropertyTable& other) const { + if (name_ != other.name_ || class_ != other.class_ || + count_ != other.count_ || + properties_.size() != other.properties_.size()) { + return false; + } + for (int i = 0; i < properties_.size(); ++i) { + if (*properties_[i] != *other.properties_[i]) { + return false; + } + } + return true; +} + +void PropertyTable::Copy(const PropertyTable& src) { + name_ = src.name_; + class_ = src.class_; + count_ = src.count_; + properties_.clear(); + properties_.reserve(src.properties_.size()); + for (int i = 0; i < src.properties_.size(); ++i) { + std::unique_ptr property(new Property()); + property->Copy(src.GetProperty(i)); + properties_.push_back(std::move(property)); + } +} + +void PropertyTable::SetName(const std::string& value) { name_ = value; } +const std::string& PropertyTable::GetName() const { return name_; } + +void PropertyTable::SetClass(const std::string& value) { class_ = value; } +const std::string& PropertyTable::GetClass() const { return class_; } + +void PropertyTable::SetCount(int count) { count_ = count; } +int PropertyTable::GetCount() const { return count_; } + +int PropertyTable::AddProperty(std::unique_ptr property) { + properties_.push_back(std::move(property)); + return properties_.size() - 1; +} +int PropertyTable::NumProperties() const { return properties_.size(); } +const PropertyTable::Property& PropertyTable::GetProperty(int index) const { + return *properties_[index]; +} +PropertyTable::Property& PropertyTable::GetProperty(int index) { + return *properties_[index]; +} +void PropertyTable::RemoveProperty(int index) { + properties_.erase(properties_.begin() + index); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.h b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.h new file mode 100644 index 000000000..41efb0163 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table.h @@ -0,0 +1,243 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_METADATA_PROPERTY_TABLE_H_ +#define DRACO_METADATA_PROPERTY_TABLE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include +#include +#include + +namespace draco { + +// Describes a property table as defined in the EXT_structural_metadata glTF +// extension, including property table schema and table properties (columns). +class PropertyTable { + public: + // Describes property table schema in the form of a JSON object. + struct Schema { + // JSON object of the schema. + // TODO(vytyaz): Consider using a third_party/json library. Currently there + // is a conflict between Filament's assert_invariant() macro and JSON + // library's assert_invariant() method that causes compile errors in Draco + // visualization library. + class Object { + public: + enum Type { OBJECT, ARRAY, STRING, INTEGER, BOOLEAN }; + + // Constructors. + Object() : Object("") {} + explicit Object(const std::string& name) + : name_(name), type_(OBJECT), integer_(0), boolean_(false) {} + Object(const std::string& name, const std::string& value) : Object(name) { + SetString(value); + } + Object(const std::string& name, const char* value) : Object(name) { + SetString(value); + } + Object(const std::string& name, int value) : Object(name) { + SetInteger(value); + } + Object(const std::string& name, bool value) : Object(name) { + SetBoolean(value); + } + + // Methods for comparing two objects. + bool operator==(const Object& other) const; + bool operator!=(const Object& other) const { return !(*this == other); } + + // Method for copying the object. + void Copy(const Object& src); + + // Methods for getting object name and type. + const std::string& GetName() const { return name_; } + Type GetType() const { return type_; } + + // Methods for getting object value. + const std::vector& GetObjects() const { return objects_; } + const std::vector& GetArray() const { return array_; } + const std::string& GetString() const { return string_; } + int GetInteger() const { return integer_; } + bool GetBoolean() const { return boolean_; } + + // Methods for setting object value. + std::vector& SetObjects() { + type_ = OBJECT; + return objects_; + } + std::vector& SetArray() { + type_ = ARRAY; + return array_; + } + void SetString(const std::string& value) { + type_ = STRING; + string_ = value; + } + void SetInteger(int value) { + type_ = INTEGER; + integer_ = value; + } + void SetBoolean(bool value) { + type_ = BOOLEAN; + boolean_ = value; + } + + private: + std::string name_; + Type type_; + std::vector objects_; + std::vector array_; + std::string string_; + int integer_; + bool boolean_; + }; + + // Valid schema top-level JSON object name is "schema". + Schema() : json("schema") {} + + // Methods for comparing two schemas. + bool operator==(const Schema& other) const { return json == other.json; } + bool operator!=(const Schema& other) const { return !(*this == other); } + + // Valid schema top-level JSON object is required to have child objects. + bool Empty() const { return json.GetObjects().empty(); } + + // Top-level JSON object of the schema. + Object json; + }; + + // Describes a property (column) of a property table. + class Property { + public: + // Describes glTF buffer view data. + struct Data { + // Methods for comparing two data objects. + bool operator==(const Data& other) const; + bool operator!=(const Data& other) const { return !(*this == other); } + + // Buffer view data. + std::vector data; + + // Data target corresponds to the target property of the glTF bufferView + // object and classifies the type or nature of the data. + int target = 0; + }; + + // Describes offsets of the entries in property data when the data + // represents an array of strings or an array of variable-length number + // arrays. + struct Offsets { + // Methods for comparing two offsets. + bool operator==(const Offsets& other) const; + bool operator!=(const Offsets& other) const { return !(*this == other); } + + // Data containing the offset entries. + Data data; + + // Data type of the offset entries. + std::string type; + }; + + // Creates an empty property. + Property(); + + // Methods for comparing two properties. + bool operator==(const Property& other) const; + bool operator!=(const Property& other) const { return !(*this == other); } + + // Copies all data from |src| property. + void Copy(const Property& src); + + // Name of this property. + void SetName(const std::string& name); + const std::string& GetName() const; + + // Property data stores one table column worth of data. For example, when + // the data of type UINT8 is [11, 22] then the property values are 11 and 22 + // for the first and second table rows. See EXT_structural_metadata glTF + // extension documentation for more details. + Data& GetData(); + const Data& GetData() const; + + // Array offsets are used when property data contains a variable-length + // number arrays. For example, when the data is [0, 1, 2, 3, 4] and the + // array offsets are [0, 2, 5] for a two-row table, then the property value + // arrays are [0, 1] and [2, 3, 4] for the first and second table rows, + // respectively. See EXT_structural_metadata glTF extension documentation + // for more details. + const Offsets& GetArrayOffsets() const; + Offsets& GetArrayOffsets(); + + // String offsets are used when property data contains strings. For example, + // when the data is "SeaLand" and the array offsets are [0, 3, 7] for a + // two-row table, then the property strings are "Sea" and "Land" for the + // first and second table rows, respectively. See EXT_structural_metadata + // glTF extension documentation for more details. + const Offsets& GetStringOffsets() const; + Offsets& GetStringOffsets(); + + private: + std::string name_; + Data data_; + Offsets array_offsets_; + Offsets string_offsets_; + // TODO(vytyaz): Support property value modifiers min, max, offset, scale. + }; + + // Creates an empty property table. + PropertyTable(); + + // Methods for comparing two property tables. + bool operator==(const PropertyTable& other) const; + bool operator!=(const PropertyTable& other) const { + return !(*this == other); + } + + // Copies all data from |src| property table. + void Copy(const PropertyTable& src); + + // Name of this property table. + void SetName(const std::string& value); + const std::string& GetName() const; + + // Class of this property table. + void SetClass(const std::string& value); + const std::string& GetClass() const; + + // Number of rows in this property table. + void SetCount(int count); + int GetCount() const; + + // Table properties (columns). + int AddProperty(std::unique_ptr property); + int NumProperties() const; + const Property& GetProperty(int index) const; + Property& GetProperty(int index); + void RemoveProperty(int index); + + private: + std::string name_; + std::string class_; + int count_; + std::vector> properties_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_METADATA_PROPERTY_TABLE_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table_test.cc new file mode 100644 index 000000000..4d5ee2d2c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/property_table_test.cc @@ -0,0 +1,624 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/property_table.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(PropertyTableTest, TestPropertyDataDefaults) { + // Test construction of an empty property data. + draco::PropertyTable::Property::Data data; + ASSERT_TRUE(data.data.empty()); + ASSERT_EQ(data.target, 0); +} + +TEST(PropertyTableTest, TestPropertyDefaults) { + // Test construction of an empty property table property. + draco::PropertyTable::Property property; + ASSERT_TRUE(property.GetName().empty()); + ASSERT_TRUE(property.GetData().data.empty()); + { + const auto &offsets = property.GetArrayOffsets(); + ASSERT_TRUE(offsets.type.empty()); + ASSERT_TRUE(offsets.data.data.empty()); + ASSERT_EQ(offsets.data.target, 0); + } + { + const auto &offsets = property.GetStringOffsets(); + ASSERT_TRUE(offsets.type.empty()); + ASSERT_TRUE(offsets.data.data.empty()); + ASSERT_EQ(offsets.data.target, 0); + } +} + +TEST(PropertyTableTest, TestPropertyTableDefaults) { + // Test construction of an empty property table. + draco::PropertyTable table; + ASSERT_TRUE(table.GetName().empty()); + ASSERT_TRUE(table.GetClass().empty()); + ASSERT_EQ(table.GetCount(), 0); + ASSERT_EQ(table.NumProperties(), 0); +} + +TEST(PropertyTableTest, TestSchemaDefaults) { + // Test construction of an empty property table schema. + draco::PropertyTable::Schema schema; + ASSERT_TRUE(schema.Empty()); + ASSERT_EQ(schema.json.GetName(), "schema"); + ASSERT_EQ(schema.json.GetType(), + draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(schema.json.GetObjects().empty()); + ASSERT_TRUE(schema.json.GetArray().empty()); + ASSERT_TRUE(schema.json.GetString().empty()); + ASSERT_EQ(schema.json.GetInteger(), 0); + ASSERT_FALSE(schema.json.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectDefaultConstructor) { + // Test construction of an empty property table schema object. + draco::PropertyTable::Schema::Object object; + ASSERT_TRUE(object.GetName().empty()); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(object.GetObjects().empty()); + ASSERT_TRUE(object.GetArray().empty()); + ASSERT_TRUE(object.GetString().empty()); + ASSERT_EQ(object.GetInteger(), 0); + ASSERT_FALSE(object.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectNamedConstructor) { + // Test construction of a named property table schema object. + draco::PropertyTable::Schema::Object object("Flexible Demeanour"); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(object.GetObjects().empty()); +} + +TEST(PropertyTableTest, TestSchemaObjectStringConstructor) { + // Test construction of property table schema object storing a string. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", "GCU"); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::STRING); + ASSERT_EQ(object.GetString(), "GCU"); +} + +TEST(PropertyTableTest, TestSchemaObjectIntegerConstructor) { + // Test construction of property table schema object storing an integer. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", 12); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::INTEGER); + ASSERT_EQ(object.GetInteger(), 12); +} + +TEST(PropertyTableTest, TestSchemaObjectBooleanConstructor) { + // Test construction of property table schema object storing a boolean. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", true); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::BOOLEAN); + ASSERT_TRUE(object.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectSettersAndGetters) { + // Test value setters and getters of property table schema object. + typedef draco::PropertyTable::Schema::Object Object; + Object object; + ASSERT_EQ(object.GetType(), Object::OBJECT); + + object.SetArray().push_back(Object("entry", 12)); + ASSERT_EQ(object.GetType(), Object::ARRAY); + ASSERT_EQ(object.GetArray().size(), 1); + ASSERT_EQ(object.GetArray()[0].GetName(), "entry"); + ASSERT_EQ(object.GetArray()[0].GetInteger(), 12); + + object.SetObjects().push_back(Object("object", 9)); + ASSERT_EQ(object.GetType(), Object::OBJECT); + ASSERT_EQ(object.GetObjects().size(), 1); + ASSERT_EQ(object.GetObjects()[0].GetName(), "object"); + ASSERT_EQ(object.GetObjects()[0].GetInteger(), 9); + + object.SetString("matter"); + ASSERT_EQ(object.GetType(), Object::STRING); + ASSERT_EQ(object.GetString(), "matter"); + + object.SetInteger(5); + ASSERT_EQ(object.GetType(), Object::INTEGER); + ASSERT_EQ(object.GetInteger(), 5); + + object.SetBoolean(true); + ASSERT_EQ(object.GetType(), Object::BOOLEAN); + ASSERT_EQ(object.GetBoolean(), true); +} + +TEST(PropertyTableTest, TestSchemaCompare) { + typedef draco::PropertyTable::Schema Schema; + // Test comparison of two schema objects. + { + // Compare the same empty schema object. + Schema a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two empty schema objects. + Schema a; + Schema b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two schema objects with different JSON objects. + Schema a; + Schema b; + a.json.SetBoolean(true); + b.json.SetBoolean(false); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestSchemaObjectCompare) { + // Test comparison of two schema JSON objects. + typedef draco::PropertyTable::Schema::Object Object; + { + // Compare the same object. + Object a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default objects. + Object a; + Object b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two objects with different names. + Object a("one"); + Object b("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different types. + Object a; + Object b; + a.SetInteger(1); + b.SetString("one"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical string-type objects. + Object a; + Object b; + a.SetString("one"); + b.SetString("one"); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different string-type objects. + Object a; + Object b; + a.SetString("one"); + b.SetString("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical integer-type objects. + Object a; + Object b; + a.SetInteger(1); + b.SetInteger(1); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different integer-type objects. + Object a; + Object b; + a.SetInteger(1); + b.SetInteger(2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical boolean-type objects. + Object a; + Object b; + a.SetBoolean(true); + b.SetBoolean(true); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different boolean-type objects. + Object a; + Object b; + a.SetBoolean(true); + b.SetBoolean(false); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical object-type objects. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("one"); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different object-type objects. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two object-type objects with different counts. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical array-type objects. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 1); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different array-type objects. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two array-type objects with different counts. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertySettersAndGetters) { + // Test setter and getter methods of the property table property. + draco::PropertyTable::Property property; + property.SetName("Unfortunate Conflict Of Evidence"); + property.GetData().data.push_back(2); + + // Check that property members can be accessed via getters. + ASSERT_EQ(property.GetName(), "Unfortunate Conflict Of Evidence"); + ASSERT_EQ(property.GetData().data.size(), 1); + ASSERT_EQ(property.GetData().data[0], 2); +} + +TEST(PropertyTableTest, TestPropertyTableSettersAndGetters) { + // Test setter and getter methods of the property table. + draco::PropertyTable table; + table.SetName("Just Read The Instructions"); + table.SetClass("General Contact Unit"); + table.SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + ASSERT_EQ(table.AddProperty(std::move(property)), 0); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + ASSERT_EQ(table.AddProperty(std::move(property)), 1); + } + + // Check that property table members can be accessed via getters. + ASSERT_EQ(table.GetName(), "Just Read The Instructions"); + ASSERT_EQ(table.GetClass(), "General Contact Unit"); + ASSERT_EQ(table.GetCount(), 456); + ASSERT_EQ(table.NumProperties(), 2); + ASSERT_EQ(table.GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(table.GetProperty(1).GetName(), "Revisionist"); + + // Check that proeprties can be removed. + table.RemoveProperty(0); + ASSERT_EQ(table.NumProperties(), 1); + ASSERT_EQ(table.GetProperty(0).GetName(), "Revisionist"); + table.RemoveProperty(0); + ASSERT_EQ(table.NumProperties(), 0); +} + +TEST(PropertyTableTest, TestPropertyCopy) { + // Test that property table property can be copied. + draco::PropertyTable::Property property; + property.SetName("Unfortunate Conflict Of Evidence"); + property.GetData().data.push_back(2); + + // Make a copy. + draco::PropertyTable::Property copy; + copy.Copy(property); + + // Check the copy. + ASSERT_EQ(copy.GetName(), "Unfortunate Conflict Of Evidence"); + ASSERT_EQ(copy.GetData().data.size(), 1); + ASSERT_EQ(copy.GetData().data[0], 2); +} + +TEST(PropertyTableTest, TestPropertyTableCopy) { + // Test that property table can be copied. + draco::PropertyTable table; + table.SetName("Just Read The Instructions"); + table.SetClass("General Contact Unit"); + table.SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + table.AddProperty(std::move(property)); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + table.AddProperty(std::move(property)); + } + + // Make a copy. + draco::PropertyTable copy; + copy.Copy(table); + + // Check the copy. + ASSERT_EQ(copy.GetName(), "Just Read The Instructions"); + ASSERT_EQ(copy.GetClass(), "General Contact Unit"); + ASSERT_EQ(copy.GetCount(), 456); + ASSERT_EQ(copy.NumProperties(), 2); + ASSERT_EQ(copy.GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(copy.GetProperty(1).GetName(), "Revisionist"); +} + +TEST(PropertyTableTest, TestPropertyDataCompare) { + // Test comparison of two property data objects. + typedef draco::PropertyTable::Property::Data Data; + { + // Compare the same data object. + Data a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default data objects. + Data a; + Data b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two data objects with different targets. + Data a; + Data b; + a.target = 1; + b.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two data objects with different data vectors. + Data a; + Data b; + a.data = {1}; + a.data = {2}; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyOffsets) { + // Test comparison of two property offsets. + typedef draco::PropertyTable::Property::Offsets Offsets; + { + // Compare the same offsets object. + Offsets a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default offsets objects. + Offsets a; + Offsets b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two offsets objects with different types. + Offsets a; + Offsets b; + a.type = 1; + b.type = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two offsets objects with different data objects. + Offsets a; + Offsets b; + a.data.target = 1; + b.data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyCompare) { + // Test comparison of two properties. + typedef draco::PropertyTable::Property Property; + { + // Compare the same property object. + Property a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default property objects. + Property a; + Property b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property objects with different names. + Property a; + Property b; + a.SetName("one"); + b.SetName("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different data. + Property a; + Property b; + a.GetData().target = 1; + b.GetData().target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different array offsets. + Property a; + Property b; + a.GetArrayOffsets().data.target = 1; + b.GetArrayOffsets().data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different string offsets. + Property a; + Property b; + a.GetStringOffsets().data.target = 1; + b.GetStringOffsets().data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyTableCompare) { + // Test comparison of two property tables. + typedef draco::PropertyTable PropertyTable; + typedef draco::PropertyTable::Property Property; + { + // Compare the same property table object. + PropertyTable a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default property tables. + PropertyTable a; + PropertyTable b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property tables with different names. + PropertyTable a; + PropertyTable b; + a.SetName("one"); + b.SetName("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different classes. + PropertyTable a; + PropertyTable b; + a.SetClass("one"); + b.SetClass("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different counts. + PropertyTable a; + PropertyTable b; + a.SetCount(1); + b.SetCount(2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with identical properties. + PropertyTable a; + PropertyTable b; + a.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property tables with different number of properties. + PropertyTable a; + PropertyTable b; + a.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different properties. + PropertyTable a; + PropertyTable b; + std::unique_ptr p1(new Property); + std::unique_ptr p2(new Property); + p1->SetName("one"); + p2->SetName("two"); + a.AddProperty(std::move(p1)); + b.AddProperty(std::move(p2)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.cc new file mode 100644 index 000000000..48fff2b25 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.cc @@ -0,0 +1,74 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/structural_metadata.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +StructuralMetadata::StructuralMetadata() {} + +bool StructuralMetadata::operator==(const StructuralMetadata &other) const { + return property_table_schema_ == other.property_table_schema_ && + property_tables_ == other.property_tables_; +} + +void StructuralMetadata::Copy(const StructuralMetadata &src) { + property_table_schema_.json.Copy(src.property_table_schema_.json); + property_tables_.resize(src.property_tables_.size()); + for (int i = 0; i < property_tables_.size(); ++i) { + property_tables_[i] = std::unique_ptr(new PropertyTable()); + property_tables_[i]->Copy(*src.property_tables_[i]); + } +} + +void StructuralMetadata::SetPropertyTableSchema( + const PropertyTable::Schema &schema) { + property_table_schema_ = schema; +} + +const PropertyTable::Schema &StructuralMetadata::GetPropertyTableSchema() + const { + return property_table_schema_; +} + +int StructuralMetadata::AddPropertyTable( + std::unique_ptr property_table) { + property_tables_.push_back(std::move(property_table)); + return property_tables_.size() - 1; +} + +int StructuralMetadata::NumPropertyTables() const { + return property_tables_.size(); +} + +const PropertyTable &StructuralMetadata::GetPropertyTable(int index) const { + return *property_tables_[index]; +} + +PropertyTable &StructuralMetadata::GetPropertyTable(int index) { + return *property_tables_[index]; +} + +void StructuralMetadata::RemovePropertyTable(int index) { + property_tables_.erase(property_tables_.begin() + index); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.h b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.h new file mode 100644 index 000000000..24756c70c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata.h @@ -0,0 +1,64 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_METADATA_STRUCTURAL_METADATA_H_ +#define DRACO_METADATA_STRUCTURAL_METADATA_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include +#include +#include + +#include "draco/metadata/property_table.h" + +namespace draco { + +// Holds data associated with EXT_structural_metadata glTF extension. +class StructuralMetadata { + public: + StructuralMetadata(); + + // Methods for comparing two structural metadata objects. + bool operator==(const StructuralMetadata &other) const; + bool operator!=(const StructuralMetadata &other) const { + return !(*this == other); + } + + // Copies |src| structural metadata into this object. + void Copy(const StructuralMetadata &src); + + // Property table schema. + void SetPropertyTableSchema(const PropertyTable::Schema &schema); + const PropertyTable::Schema &GetPropertyTableSchema() const; + + // Property tables. + int AddPropertyTable(std::unique_ptr property_table); + int NumPropertyTables() const; + const PropertyTable &GetPropertyTable(int index) const; + PropertyTable &GetPropertyTable(int index); + void RemovePropertyTable(int index); + + private: + // Property table schema and property tables. + PropertyTable::Schema property_table_schema_; + std::vector> property_tables_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_METADATA_STRUCTURAL_METADATA_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata_test.cc new file mode 100644 index 000000000..d0429a568 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/metadata/structural_metadata_test.cc @@ -0,0 +1,170 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/structural_metadata.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(StructuralMetadataTest, TestCopy) { + // Tests copying of structural metadata. + draco::StructuralMetadata structural_metadata; + + // Add property table schema to structural metadata. + draco::PropertyTable::Schema schema; + schema.json.SetString("Culture"); + structural_metadata.SetPropertyTableSchema(schema); + + // Add property table to structural metadata. + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Just Read The Instructions"); + table->SetClass("General Contact Unit"); + table->SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + table->AddProperty(std::move(property)); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + table->AddProperty(std::move(property)); + } + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 0); + + // Copy the structural metadata. + draco::StructuralMetadata copy; + copy.Copy(structural_metadata); + + // Check that the structural metadata property table schema has been copied. + ASSERT_EQ(copy.GetPropertyTableSchema().json.GetString(), "Culture"); + + // Check that the structural metadata property table has been copied. + ASSERT_EQ(copy.NumPropertyTables(), 1); + ASSERT_EQ(copy.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(copy.GetPropertyTable(0).GetClass(), "General Contact Unit"); + ASSERT_EQ(copy.GetPropertyTable(0).GetCount(), 456); + ASSERT_EQ(copy.GetPropertyTable(0).NumProperties(), 2); + ASSERT_EQ(copy.GetPropertyTable(0).GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(copy.GetPropertyTable(0).GetProperty(1).GetName(), "Revisionist"); +} + +TEST(StructuralMetadataTest, TestPropertyTables) { + // Tests adding and removing of property tables to structural metadata. + draco::StructuralMetadata structural_metadata; + + // Check that property tables can be added. + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Just Read The Instructions"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 0); + } + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("So Much For Subtlety"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 1); + } + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Of Course I Still Love You"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 2); + } + draco::StructuralMetadata &sm = structural_metadata; + + // Check that the property tables can be removed. + ASSERT_EQ(sm.NumPropertyTables(), 3); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(sm.GetPropertyTable(1).GetName(), "So Much For Subtlety"); + ASSERT_EQ(sm.GetPropertyTable(2).GetName(), "Of Course I Still Love You"); + + sm.RemovePropertyTable(1); + ASSERT_EQ(sm.NumPropertyTables(), 2); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(sm.GetPropertyTable(1).GetName(), "Of Course I Still Love You"); + + sm.RemovePropertyTable(1); + ASSERT_EQ(sm.NumPropertyTables(), 1); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + + sm.RemovePropertyTable(0); + ASSERT_EQ(sm.NumPropertyTables(), 0); +} + +TEST(StructuralMetadataTest, TestCompare) { + // Test comparison of two structural metadata objects. + typedef draco::PropertyTable PropertyTable; + { + // Compare the same structural metadata object. + draco::StructuralMetadata a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two identical structural metadata objects. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two structural metadata objects with different schemas. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + PropertyTable::Schema s1; + PropertyTable::Schema s2; + s1.json.SetString("one"); + s2.json.SetString("two"); + a.SetPropertyTableSchema(s1); + b.SetPropertyTableSchema(s2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different number of proeprty tables. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + a.AddPropertyTable(std::unique_ptr(new PropertyTable())); + b.AddPropertyTable(std::unique_ptr(new PropertyTable())); + b.AddPropertyTable(std::unique_ptr(new PropertyTable())); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different proeprty tables. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + auto p1 = std::unique_ptr(new PropertyTable()); + auto p2 = std::unique_ptr(new PropertyTable()); + p1->SetName("one"); + p2->SetName("two"); + a.AddPropertyTable(std::move(p1)); + b.AddPropertyTable(std::move(p2)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.cc b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.cc index a9f9ea2af..039c4f201 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.cc @@ -16,11 +16,41 @@ #include #include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/point_attribute.h" +#endif namespace draco { PointCloud::PointCloud() : num_points_(0) {} +#ifdef DRACO_TRANSCODER_SUPPORTED +void PointCloud::Copy(const PointCloud &src) { + num_points_ = src.num_points_; + for (int i = 0; i < GeometryAttribute::NAMED_ATTRIBUTES_COUNT; ++i) { + named_attribute_index_[i] = src.named_attribute_index_[i]; + } + attributes_.resize(src.attributes_.size()); + for (int i = 0; i < src.attributes_.size(); ++i) { + attributes_[i] = std::unique_ptr(new PointAttribute()); + attributes_[i]->CopyFrom(*src.attributes_[i]); + } + CopyMetadata(src); +} + +void PointCloud::CopyMetadata(const PointCloud &src) { + if (src.metadata_ == nullptr) { + metadata_ = nullptr; + } else { + // Copy base metadata. + const GeometryMetadata *const metadata = src.metadata_.get(); + metadata_.reset(new GeometryMetadata(*metadata)); + } +} +#endif + int32_t PointCloud::NumNamedAttributes(GeometryAttribute::Type type) const { if (type == GeometryAttribute::INVALID || type >= GeometryAttribute::NAMED_ATTRIBUTES_COUNT) { @@ -253,11 +283,16 @@ bool PointCloud::DeduplicateAttributeValues() { } #endif -// TODO(xiaoxumeng): Consider to cash the BBox. +// TODO(b/199760503): Consider to cache the BBox. BoundingBox PointCloud::ComputeBoundingBox() const { BoundingBox bounding_box; auto pc_att = GetNamedAttribute(GeometryAttribute::POSITION); - // TODO(xiaoxumeng): Make the BoundingBox a template type, it may not be easy + if (pc_att == nullptr) { + // Return default invalid bounding box. + return bounding_box; + } + + // TODO(b/199760503): Make the BoundingBox a template type, it may not be easy // because PointCloud is not a template. // Or simply add some preconditioning here to make sure the position attribute // is valid, because the current code works only if the position attribute is diff --git a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.h b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.h index d11bd47a3..17d0bc392 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud.h @@ -31,6 +31,11 @@ class PointCloud { PointCloud(); virtual ~PointCloud() = default; +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies all data from the |src| point cloud. + void Copy(const PointCloud &src); +#endif + // Returns the number of named attributes of a given type. int32_t NumNamedAttributes(GeometryAttribute::Type type) const; @@ -185,6 +190,11 @@ class PointCloud { void set_num_points(PointIndex::ValueType num) { num_points_ = num; } protected: +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies metadata from the |src| point cloud. + void CopyMetadata(const PointCloud &src); +#endif + #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED // Applies id mapping of deduplicated points (called by DeduplicatePointIds). virtual void ApplyPointIdDeduplication( diff --git a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc index 431ae505f..90ec37962 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc @@ -14,6 +14,8 @@ // #include "draco/point_cloud/point_cloud_builder.h" +#include + namespace draco { PointCloudBuilder::PointCloudBuilder() {} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.h b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.h index cf55a728b..512b0f71f 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.h +++ b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_builder.h @@ -15,6 +15,8 @@ #ifndef DRACO_POINT_CLOUD_POINT_CLOUD_BUILDER_H_ #define DRACO_POINT_CLOUD_POINT_CLOUD_BUILDER_H_ +#include + #include "draco/point_cloud/point_cloud.h" namespace draco { @@ -37,6 +39,9 @@ namespace draco { class PointCloudBuilder { public: + // Index type of the inserted element. + typedef PointIndex ElementIndex; + PointCloudBuilder(); // Starts collecting point cloud data. @@ -71,6 +76,12 @@ class PointCloudBuilder { // used until the method Start() is called again. std::unique_ptr Finalize(bool deduplicate_points); + // Add metadata for an attribute. + void AddAttributeMetadata(int32_t att_id, + std::unique_ptr metadata) { + point_cloud_->AddAttributeMetadata(att_id, std::move(metadata)); + } + private: std::unique_ptr point_cloud_; }; diff --git a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_test.cc index 4e9460370..1cd780db2 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_test.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/point_cloud/point_cloud_test.cc @@ -14,6 +14,9 @@ // #include "draco/point_cloud/point_cloud.h" +#include +#include + #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/metadata/geometry_metadata.h" @@ -25,6 +28,57 @@ class PointCloudTest : public ::testing::Test { PointCloudTest() {} }; +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(PointCloudTest, PointCloudCopy) { + // Tests that we can copy a point cloud. + std::unique_ptr pc = + draco::ReadPointCloudFromTestFile("pc_kd_color.drc"); + ASSERT_NE(pc, nullptr); + + // Add metadata to the point cloud. + std::unique_ptr metadata( + new draco::GeometryMetadata()); + metadata->AddEntryInt("speed", 1050); + metadata->AddEntryString("code", "YT-1300f"); + + // Add attribute metadata. + std::unique_ptr a_metadata( + new draco::AttributeMetadata()); + a_metadata->set_att_unique_id(pc->attribute(0)->unique_id()); + a_metadata->AddEntryInt("attribute_test", 3); + metadata->AddAttributeMetadata(std::move(a_metadata)); + pc->AddMetadata(std::move(metadata)); + + // Create a copy of the point cloud. + draco::PointCloud pc_copy; + pc_copy.Copy(*pc); + + // Check the point cloud data. + ASSERT_EQ(pc->num_points(), pc_copy.num_points()); + ASSERT_EQ(pc->num_attributes(), pc_copy.num_attributes()); + for (int i = 0; i < pc->num_attributes(); ++i) { + ASSERT_EQ(pc->attribute(i)->attribute_type(), + pc_copy.attribute(i)->attribute_type()); + } + + // Check the point cloud metadata. + int speed; + std::string code; + ASSERT_NE(pc->GetMetadata(), nullptr); + ASSERT_TRUE(pc->GetMetadata()->GetEntryInt("speed", &speed)); + ASSERT_TRUE(pc->GetMetadata()->GetEntryString("code", &code)); + ASSERT_EQ(speed, 1050); + ASSERT_EQ(code, "YT-1300f"); + + const auto *const att_metadata_copy = + pc->GetMetadata()->GetAttributeMetadataByUniqueId(0); + ASSERT_NE(att_metadata_copy, nullptr); + int att_test; + ASSERT_TRUE(att_metadata_copy->GetEntryInt("attribute_test", &att_test)); + ASSERT_EQ(att_test, 3); +} +#endif + TEST_F(PointCloudTest, TestAttributeDeletion) { draco::PointCloud pc; // Test whether we can correctly delete an attribute from a point cloud. diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.cc new file mode 100644 index 000000000..bcd49996a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.cc @@ -0,0 +1,45 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/instance_array.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +namespace draco { + +void InstanceArray::Copy(const InstanceArray &other) { + instances_.resize(other.instances_.size()); + for (int i = 0; i < instances_.size(); i++) { + instances_[i].trs.Copy(other.instances_[i].trs); + } +} + +Status InstanceArray::AddInstance(const Instance &instance) { + // Check that the |instance.trs| does not have the transformation matrix set, + // because the EXT_mesh_gpu_instancing glTF extension dictates that only the + // individual TRS vectors are stored. + if (instance.trs.MatrixSet()) { + return ErrorStatus("Instance must have no matrix set."); + } + + // Move the |instance| to the end of the instances vector. + instances_.push_back(instance); + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.h new file mode 100644 index 000000000..fb1fbde1e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_INSTANCE_ARRAY_H_ +#define DRACO_SCENE_INSTANCE_ARRAY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/vector_d.h" +#include "draco/scene/trs_matrix.h" + +namespace draco { + +// Describes a mesh group instancing array that includes TRS transformation +// for multiple instance positions and possibly other custom instance attributes +// (not yet supported), following the EXT_mesh_gpu_instancing glTF extension. +class InstanceArray { + public: + struct Instance { + // Translation, rotation, and scale vectors. + TrsMatrix trs; + // TODO(vytyaz): Support custom instance attributes, e.g., _ID, _COLOR, etc. + }; + + InstanceArray() = default; + + void Copy(const InstanceArray &other); + + // Adds one |instance| into this mesh group instance array where the + // |instance.trs| may have optional translation, rotation, and scale set. + Status AddInstance(const Instance &instance); + + // Returns the count of instances in this mesh group instance array. + int NumInstances() const { return instances_.size(); } + + // Returns an instance from this mesh group instance array. + const Instance &GetInstance(int index) const { return instances_[index]; } + + private: + std::vector instances_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_INSTANCE_ARRAY_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array_test.cc new file mode 100644 index 000000000..d3802f901 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/instance_array_test.cc @@ -0,0 +1,179 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/instance_array.h" + +#include +#include + +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(InstanceArrayTest, TestInstance) { + // Test construction of an empty draco::InstanceArray::Instance struct. + const draco::InstanceArray::Instance instance; + ASSERT_FALSE(instance.trs.TranslationSet()); + ASSERT_FALSE(instance.trs.RotationSet()); + ASSERT_FALSE(instance.trs.ScaleSet()); + ASSERT_FALSE(instance.trs.MatrixSet()); +} + +TEST(InstanceArrayTest, TestDefaults) { + // Test construction of an empty draco::InstanceArray object. + const draco::InstanceArray array; + ASSERT_EQ(array.NumInstances(), 0); +} + +TEST(InstanceArrayTest, TestAddInstance) { + // Test population of draco::InstanceArray object with instances. + draco::InstanceArray array; + + // Create an instance and set its transformation TRS vectors. + const Eigen::Vector3d translation_0(1.0, 2.0, 3.0); + const Eigen::Quaterniond rotation_0(4.0, 5.0, 6.0, 7.0); + const Eigen::Vector3d scale_0(8.0, 9.0, 10.0); + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(translation_0); + instance_0.trs.SetRotation(rotation_0); + instance_0.trs.SetScale(scale_0); + + // Create another instance. + const Eigen::Vector3d translation_1(1.1, 2.1, 3.1); + const Eigen::Quaterniond rotation_1(4.1, 5.1, 6.1, 7.1); + const Eigen::Vector3d scale_1(8.1, 9.1, 10.1); + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(translation_1); + instance_1.trs.SetRotation(rotation_1); + instance_1.trs.SetScale(scale_1); + + // Add two instances to instance array. + DRACO_ASSERT_OK(array.AddInstance(instance_0)); + DRACO_ASSERT_OK(array.AddInstance(instance_1)); + + // Check that the instances have been added. + ASSERT_EQ(array.NumInstances(), 2); + + // Check transformation of the first instance. + const draco::TrsMatrix &trs_0 = array.GetInstance(0).trs; + ASSERT_TRUE(trs_0.TranslationSet()); + ASSERT_TRUE(trs_0.RotationSet()); + ASSERT_TRUE(trs_0.ScaleSet()); + ASSERT_FALSE(trs_0.MatrixSet()); + ASSERT_EQ(trs_0.Translation().value(), translation_0); + ASSERT_EQ(trs_0.Rotation().value(), rotation_0); + ASSERT_EQ(trs_0.Scale().value(), scale_0); + + // Check transformation of the second instance. + const draco::TrsMatrix &trs_1 = array.GetInstance(1).trs; + ASSERT_TRUE(trs_1.TranslationSet()); + ASSERT_TRUE(trs_1.RotationSet()); + ASSERT_TRUE(trs_1.ScaleSet()); + ASSERT_FALSE(trs_1.MatrixSet()); + ASSERT_EQ(trs_1.Translation().value(), translation_1); + ASSERT_EQ(trs_1.Rotation().value(), rotation_1); + ASSERT_EQ(trs_1.Scale().value(), scale_1); +} + +TEST(InstanceArrayTest, TestAddInstanceWithoutTransform) { + // Test that instance without any transformation can be added. + draco::InstanceArray array; + + // Do not set any transformation. + draco::InstanceArray::Instance instance; + + // Check that such instance can be added. + DRACO_ASSERT_OK(array.AddInstance(instance)); +} + +TEST(InstanceArrayTest, TestAddInstanceWithoutScale) { + // Test that instance without scale can be added. + draco::InstanceArray array; + + // Set only instance translation and rotation. + draco::InstanceArray::Instance instance; + instance.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + + // Check that such instance can be added. + DRACO_ASSERT_OK(array.AddInstance(instance)); +} + +TEST(InstanceArrayTest, TestAddInstanceWithMatrixFails) { + // Test that instance without scale cannot be added. + draco::InstanceArray array; + + // Set TRS vectors, as well as the matrix. + draco::InstanceArray::Instance instance; + instance.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + instance.trs.SetScale(Eigen::Vector3d(8.0, 9.0, 10.0)); + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0; + // clang-format on + instance.trs.SetMatrix(matrix); + + // Check that such instance cannot be added. + const draco::Status status = array.AddInstance(instance); + ASSERT_FALSE(status.ok()); + ASSERT_EQ(status.error_msg_string(), "Instance must have no matrix set."); +} + +TEST(InstanceArrayTest, TestCopy) { + // Test copying of draco::InstanceArray object. + draco::InstanceArray array; + + // Create an instance and set its transformation TRS vectors. + const Eigen::Vector3d translation_0(1.0, 2.0, 3.0); + const Eigen::Quaterniond rotation_0(4.0, 5.0, 6.0, 7.0); + const Eigen::Vector3d scale_0(8.0, 9.0, 10.0); + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(translation_0); + instance_0.trs.SetRotation(rotation_0); + instance_0.trs.SetScale(scale_0); + + // Create another instance. + const Eigen::Vector3d translation_1(1.1, 2.1, 3.1); + const Eigen::Quaterniond rotation_1(4.1, 5.1, 6.1, 7.1); + const Eigen::Vector3d scale_1(8.1, 9.1, 10.1); + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(translation_1); + instance_1.trs.SetRotation(rotation_1); + instance_1.trs.SetScale(scale_1); + + // Add two instances to the instance array. + DRACO_ASSERT_OK(array.AddInstance(instance_0)); + DRACO_ASSERT_OK(array.AddInstance(instance_1)); + + // Create a copy of the populated instance array object. + draco::InstanceArray copy; + copy.Copy(array); + + // Check that the instances have been copied. + ASSERT_EQ(copy.NumInstances(), 2); + ASSERT_EQ(copy.GetInstance(0).trs, instance_0.trs); + ASSERT_EQ(copy.GetInstance(1).trs, instance_1.trs); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/light.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/light.cc new file mode 100644 index 000000000..1fef98c0c --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/light.cc @@ -0,0 +1,45 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/light.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +#include "draco/core/constants.h" + +namespace draco { + +Light::Light() + : color_(1.0f, 1.0f, 1.0f), + intensity_(1.0), + type_(POINT), + range_(std::numeric_limits::max()), // Infinity. + inner_cone_angle_(0.0), + outer_cone_angle_(DRACO_PI / 4.0) {} + +void Light::Copy(const Light &light) { + name_ = light.name_; + color_ = light.color_; + intensity_ = light.intensity_; + type_ = light.type_; + range_ = light.range_; + inner_cone_angle_ = light.inner_cone_angle_; + outer_cone_angle_ = light.outer_cone_angle_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/light.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/light.h new file mode 100644 index 000000000..5ff0d4a6b --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/light.h @@ -0,0 +1,81 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_LIGHT_H_ +#define DRACO_SCENE_LIGHT_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/vector_d.h" + +namespace draco { + +// Describes a light in a scene according to the KHR_lights_punctual extension. +class Light { + public: + enum Type { DIRECTIONAL, POINT, SPOT }; + + Light(); + + void Copy(const Light &light); + + // Name. + void SetName(const std::string &name) { name_ = name; } + const std::string &GetName() const { return name_; } + + // Color. + void SetColor(const Vector3f &color) { color_ = color; } + const Vector3f &GetColor() const { return color_; } + + // Intensity. + void SetIntensity(double intensity) { intensity_ = intensity; } + double GetIntensity() const { return intensity_; } + + // Type. + void SetType(Type type) { type_ = type; } + Type GetType() const { return type_; } + + // Range. + void SetRange(double range) { range_ = range; } + double GetRange() const { return range_; } + + // Inner cone angle. + void SetInnerConeAngle(double angle) { inner_cone_angle_ = angle; } + double GetInnerConeAngle() const { return inner_cone_angle_; } + + // Outer cone angle. + void SetOuterConeAngle(double angle) { outer_cone_angle_ = angle; } + double GetOuterConeAngle() const { return outer_cone_angle_; } + + private: + std::string name_; + Vector3f color_; + double intensity_; + Type type_; + + // The range is only applicable to lights with Type::POINT or Type::SPOT. + double range_; + + // The cone angles are only applicable to lights with Type::SPOT. + double inner_cone_angle_; + double outer_cone_angle_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_LIGHT_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/light_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/light_test.cc new file mode 100644 index 000000000..bc24a14ad --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/light_test.cc @@ -0,0 +1,64 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/light.h" + +#include + +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(LightTest, TestDefaults) { + // Text constructing draco::Light object with default properties. + const draco::Light light; + ASSERT_EQ(light.GetName(), ""); + ASSERT_EQ(light.GetColor(), draco::Vector3f(1.0f, 1.0f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::POINT); + ASSERT_EQ(light.GetRange(), std::numeric_limits::max()); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_EQ(light.GetOuterConeAngle(), DRACO_PI / 4.0); +} + +TEST(LightTest, TestCopy) { + // Test copying of draco::Light object. + draco::Light light; + light.SetName("The Star of Earendil"); + light.SetColor(draco::Vector3f(0.90, 0.97, 1.00)); + light.SetIntensity(5.0); + light.SetType(draco::Light::SPOT); + light.SetRange(1000.0); + light.SetInnerConeAngle(DRACO_PI / 8.0); + light.SetOuterConeAngle(DRACO_PI / 2.0); + + // Create a copy of the initialized light and check all properties. + draco::Light copy; + copy.Copy(light); + ASSERT_EQ(copy.GetName(), "The Star of Earendil"); + ASSERT_EQ(copy.GetColor(), draco::Vector3f(0.90, 0.97, 1.00)); + ASSERT_EQ(copy.GetIntensity(), 5.0); + ASSERT_EQ(copy.GetType(), draco::Light::SPOT); + ASSERT_EQ(copy.GetRange(), 1000.0); + ASSERT_EQ(copy.GetInnerConeAngle(), DRACO_PI / 8.0); + ASSERT_EQ(copy.GetOuterConeAngle(), DRACO_PI / 2.0); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group.h new file mode 100644 index 000000000..852318f16 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group.h @@ -0,0 +1,138 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_MESH_GROUP_H_ +#define DRACO_SCENE_MESH_GROUP_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/macros.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +// This class is used to hold ordered mesh instances that refer to one or more +// base meshes, materials, and materials variants mappings. +class MeshGroup { + public: + // Stores a mapping from material index to materials variant indices. Each + // mesh instance may have multiple such mappings associated with it. See glTF + // extension KHR_materials_variants for more details. + struct MaterialsVariantsMapping { + MaterialsVariantsMapping() = delete; + MaterialsVariantsMapping(int material, const std::vector &variants) + : material(material), variants(variants) {} + bool operator==(const MaterialsVariantsMapping &other) const { + if (material != other.material) { + return false; + } + if (variants != other.variants) { + return false; + } + return true; + } + bool operator!=(const MaterialsVariantsMapping &other) const { + return !(*this == other); + } + int material; + std::vector variants; + }; + + // Describes mesh instance stored in a mesh group, including base mesh index, + // material index, and materials variants mappings. + struct MeshInstance { + MeshInstance() = delete; + MeshInstance(MeshIndex mesh_index, int material_index) + : MeshInstance(mesh_index, material_index, {}) {} + MeshInstance(MeshIndex mesh_index, int material_index, + const std::vector + &materials_variants_mappings) + : mesh_index(mesh_index), + material_index(material_index), + materials_variants_mappings(materials_variants_mappings) {} + bool operator==(const MeshInstance &other) const { + if (mesh_index != other.mesh_index) { + return false; + } + if (material_index != other.material_index) { + return false; + } + if (materials_variants_mappings.size() != + other.materials_variants_mappings.size()) { + return false; + } + if (materials_variants_mappings != other.materials_variants_mappings) { + return false; + } + return true; + } + bool operator!=(const MeshInstance &other) const { + return !(*this == other); + } + MeshIndex mesh_index; + int material_index; + std::vector materials_variants_mappings; + }; + + MeshGroup() {} + + void Copy(const MeshGroup &mg) { + name_ = mg.name_; + mesh_instances_ = mg.mesh_instances_; + } + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + + void AddMeshInstance(const MeshInstance &instance) { + mesh_instances_.push_back(instance); + } + + void SetMeshInstance(int index, const MeshInstance &instance) { + mesh_instances_[index] = instance; + } + + const MeshInstance &GetMeshInstance(int index) const { + return mesh_instances_[index]; + } + + MeshInstance &GetMeshInstance(int index) { return mesh_instances_[index]; } + + int NumMeshInstances() const { return mesh_instances_.size(); } + + // Removes all mesh instances referring to base mesh at |mesh_index|. + void RemoveMeshInstances(MeshIndex mesh_index) { + int i = 0; + while (i != mesh_instances_.size()) { + if (mesh_instances_[i].mesh_index == mesh_index) { + mesh_instances_.erase(mesh_instances_.begin() + i); + } else { + i++; + } + } + } + + private: + std::string name_; + std::vector mesh_instances_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_MESH_GROUP_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group_test.cc new file mode 100644 index 000000000..76f1bf33e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/mesh_group_test.cc @@ -0,0 +1,196 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/mesh_group.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/scene/scene_indices.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +using draco::MeshGroup; +using draco::MeshIndex; + +// Test helper method generates materials variants mappings based on a |seed|. +std::vector MakeMappings(int seed) { + MeshGroup::MaterialsVariantsMapping a(10 * seed + 0, {seed + 0, seed + 1}); + MeshGroup::MaterialsVariantsMapping b(10 * seed + 1, {seed + 2, seed + 3}); + return {a, b}; +} + +TEST(MeshGroupTest, TestMeshInstanceTwoArgumentConstructor) { + MeshGroup::MeshInstance instance(MeshIndex(2), 3); + ASSERT_EQ(instance.mesh_index, MeshIndex(2)); + ASSERT_EQ(instance.material_index, 3); + ASSERT_EQ(instance.materials_variants_mappings.size(), 0); +} + +TEST(MeshGroupTest, TestMeshInstanceThreeArgumentConstructor) { + const auto mappings = MakeMappings(4); + MeshGroup::MeshInstance instance(MeshIndex(2), 3, mappings); + ASSERT_EQ(instance.mesh_index, MeshIndex(2)); + ASSERT_EQ(instance.material_index, 3); + ASSERT_EQ(instance.materials_variants_mappings, mappings); +} + +TEST(MeshGroupTest, TestMeshInstanceEqualsOperator) { + MeshGroup::MeshInstance instance_a(MeshIndex(2), 3, MakeMappings(4)); + MeshGroup::MeshInstance instance_b(MeshIndex(2), 3, MakeMappings(4)); + ASSERT_TRUE(instance_a == instance_b); + ASSERT_FALSE(instance_a != instance_b); + + MeshGroup::MeshInstance instance_c(MeshIndex(1), 3, MakeMappings(4)); + MeshGroup::MeshInstance instance_d(MeshIndex(2), 1, MakeMappings(4)); + MeshGroup::MeshInstance instance_e(MeshIndex(2), 3, MakeMappings(1)); + ASSERT_FALSE(instance_a == instance_c); + ASSERT_FALSE(instance_a == instance_d); + ASSERT_FALSE(instance_a == instance_e); + ASSERT_TRUE(instance_a != instance_c); + ASSERT_TRUE(instance_a != instance_d); + ASSERT_TRUE(instance_a != instance_e); +} + +TEST(MeshGroupTest, TestRemoveMeshInstanceWithNoOccurrences) { + // Test that no mesh instances are removed from mesh group when removing the + // instances by the base mesh index that is not in the mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(3), 0, {}}); + + // Try to remove mesh that is not in the mesh group. + mesh_group.RemoveMeshInstances(MeshIndex(2)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 2); + ASSERT_EQ(mesh_group.GetMeshInstance(0).mesh_index, MeshIndex(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(1).mesh_index, MeshIndex(3)); +} + +TEST(MeshGroupTest, TestRemoveTheOnlyMeshInstance) { + // Test that the only mesh instance can be removed from mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + MeshGroup::MaterialsVariantsMapping mapping(70, {0, 1}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, {mapping}}); + + // Remove a mesh instance. + mesh_group.RemoveMeshInstances(MeshIndex(7)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 0); +} + +TEST(MeshGroupTest, TestRemoveOneMeshInstances) { + // Test a mesh instance can be removed from mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(3), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(5), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(7), 0, {}}); + + // Remove a mesh. + mesh_group.RemoveMeshInstances(MeshIndex(3)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + ASSERT_EQ(mesh_group.GetMeshInstance(0).mesh_index, MeshIndex(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(1).mesh_index, MeshIndex(5)); + ASSERT_EQ(mesh_group.GetMeshInstance(2).mesh_index, MeshIndex(7)); +} + +TEST(MeshGroupTest, TestRemoveThreeMeshInstances) { + // Test that multiple mesh instances can be removed from a mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(3), 30, MakeMappings(3)}); + mesh_group.AddMeshInstance({MeshIndex(5), 50, MakeMappings(5)}); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, MakeMappings(7)}); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + + // Remove mesh instances. + mesh_group.RemoveMeshInstances(MeshIndex(1)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + const MeshGroup::MeshInstance mi0 = mesh_group.GetMeshInstance(0); + const MeshGroup::MeshInstance mi1 = mesh_group.GetMeshInstance(1); + const MeshGroup::MeshInstance mi2 = mesh_group.GetMeshInstance(2); + ASSERT_EQ(mi0.mesh_index, MeshIndex(3)); + ASSERT_EQ(mi1.mesh_index, MeshIndex(5)); + ASSERT_EQ(mi2.mesh_index, MeshIndex(7)); + ASSERT_EQ(mi0.material_index, 30); + ASSERT_EQ(mi1.material_index, 50); + ASSERT_EQ(mi2.material_index, 70); + ASSERT_EQ(mi0.materials_variants_mappings, MakeMappings(3)); + ASSERT_EQ(mi1.materials_variants_mappings, MakeMappings(5)); + ASSERT_EQ(mi2.materials_variants_mappings, MakeMappings(7)); +} + +TEST(MeshGroupTest, TestCopy) { + // Tests that a mesh group can be copied. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.SetName("Mesh-1-3-5-7"); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(3), 30, MakeMappings(3)}); + mesh_group.AddMeshInstance({MeshIndex(5), 50, MakeMappings(5)}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, MakeMappings(7)}); + + // Verify source MeshGroup. + ASSERT_EQ(mesh_group.GetName(), "Mesh-1-3-5-7"); + ASSERT_EQ(mesh_group.NumMeshInstances(), 4); + const MeshGroup::MeshInstance mi0 = mesh_group.GetMeshInstance(0); + const MeshGroup::MeshInstance mi1 = mesh_group.GetMeshInstance(1); + const MeshGroup::MeshInstance mi2 = mesh_group.GetMeshInstance(2); + const MeshGroup::MeshInstance mi3 = mesh_group.GetMeshInstance(3); + ASSERT_EQ(mi0.mesh_index, MeshIndex(1)); + ASSERT_EQ(mi1.mesh_index, MeshIndex(3)); + ASSERT_EQ(mi2.mesh_index, MeshIndex(5)); + ASSERT_EQ(mi3.mesh_index, MeshIndex(7)); + ASSERT_EQ(mi0.material_index, 10); + ASSERT_EQ(mi1.material_index, 30); + ASSERT_EQ(mi2.material_index, 50); + ASSERT_EQ(mi3.material_index, 70); + ASSERT_EQ(mi0.materials_variants_mappings, MakeMappings(1)); + ASSERT_EQ(mi1.materials_variants_mappings, MakeMappings(3)); + ASSERT_EQ(mi2.materials_variants_mappings, MakeMappings(5)); + ASSERT_EQ(mi3.materials_variants_mappings, MakeMappings(7)); + + MeshGroup copy; + copy.Copy(mesh_group); + + // Verify that Copy worked. + ASSERT_EQ(mesh_group.GetName(), copy.GetName()); + ASSERT_EQ(mesh_group.NumMeshInstances(), copy.NumMeshInstances()); + ASSERT_EQ(mesh_group.GetMeshInstance(0), copy.GetMeshInstance(0)); + ASSERT_EQ(mesh_group.GetMeshInstance(1), copy.GetMeshInstance(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(2), copy.GetMeshInstance(2)); + ASSERT_EQ(mesh_group.GetMeshInstance(3), copy.GetMeshInstance(3)); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.cc new file mode 100644 index 000000000..9ad574835 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.cc @@ -0,0 +1,174 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/macros.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +void Scene::Copy(const Scene &s) { + meshes_.resize(s.meshes_.size()); + for (MeshIndex i(0); i < meshes_.size(); ++i) { + meshes_[i] = std::unique_ptr(new Mesh()); + meshes_[i]->Copy(*s.meshes_[i]); + } + + mesh_groups_.resize(s.mesh_groups_.size()); + for (MeshGroupIndex i(0); i < mesh_groups_.size(); ++i) { + mesh_groups_[i] = std::unique_ptr(new MeshGroup()); + mesh_groups_[i]->Copy(*s.mesh_groups_[i]); + } + + nodes_.resize(s.nodes_.size()); + for (SceneNodeIndex i(0); i < nodes_.size(); ++i) { + nodes_[i] = std::unique_ptr(new SceneNode()); + nodes_[i]->Copy(*s.nodes_[i]); + } + + root_node_indices_ = s.root_node_indices_; + + animations_.resize(s.animations_.size()); + for (AnimationIndex i(0); i < animations_.size(); ++i) { + animations_[i] = std::unique_ptr(new Animation()); + animations_[i]->Copy(*s.animations_[i]); + } + + skins_.resize(s.skins_.size()); + for (SkinIndex i(0); i < skins_.size(); ++i) { + skins_[i] = std::unique_ptr(new Skin()); + skins_[i]->Copy(*s.skins_[i]); + } + + lights_.resize(s.lights_.size()); + for (LightIndex i(0); i < lights_.size(); ++i) { + lights_[i] = std::unique_ptr(new Light()); + lights_[i]->Copy(*s.lights_[i]); + } + + instance_arrays_.resize(s.instance_arrays_.size()); + for (InstanceArrayIndex i(0); i < instance_arrays_.size(); ++i) { + instance_arrays_[i] = std::unique_ptr(new InstanceArray()); + instance_arrays_[i]->Copy(*s.instance_arrays_[i]); + } + + material_library_.Copy(s.material_library_); + +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copy non-material textures. + non_material_texture_library_.Copy(s.non_material_texture_library_); + + // Update pointers to non-material textures in mesh feature ID sets of all + // scene meshes. + if (non_material_texture_library_.NumTextures() != 0) { + const auto texture_to_index_map = + s.non_material_texture_library_.ComputeTextureToIndexMap(); + for (MeshIndex i(0); i < NumMeshes(); ++i) { + for (MeshFeaturesIndex j(0); j < GetMesh(i).NumMeshFeatures(); ++j) { + Mesh::UpdateMeshFeaturesTexturePointer(texture_to_index_map, + &non_material_texture_library_, + &GetMesh(i).GetMeshFeatures(j)); + } + } + } +#endif // DRACO_TRANSCODER_SUPPORTED + + // Copy structural metadata. + structural_metadata_.Copy(s.structural_metadata_); +} + +Status Scene::RemoveMesh(MeshIndex index) { + // Remove base mesh at |index| from |meshes_| and corresponding material index + // from |mesh_material_indices_|. + const int new_num_meshes = meshes_.size() - 1; + for (MeshIndex i(index); i < new_num_meshes; i++) { + meshes_[i] = std::move(meshes_[i + 1]); + } + meshes_.resize(new_num_meshes); + + // Remove references to removed base mesh and corresponding materials from + // mesh groups, and update references to remaining base meshes in mesh groups. + for (MeshGroupIndex mgi(0); mgi < NumMeshGroups(); ++mgi) { + MeshGroup *const mesh_group = GetMeshGroup(mgi); + if (!mesh_group) { + return Status(Status::DRACO_ERROR, "MeshGroup is null."); + } + mesh_group->RemoveMeshInstances(index); + for (int i = 0; i < mesh_group->NumMeshInstances(); ++i) { + MeshGroup::MeshInstance &mesh_instance = mesh_group->GetMeshInstance(i); + if (mesh_instance.mesh_index > index && + mesh_instance.mesh_index != kInvalidMeshIndex) { + mesh_instance.mesh_index--; + } + } + } + return OkStatus(); +} + +Status Scene::RemoveMeshGroup(MeshGroupIndex index) { + // Remove mesh group at |index| from |mesh_groups_| vector. + const int new_num_mesh_groups = mesh_groups_.size() - 1; + for (MeshGroupIndex i(index); i < new_num_mesh_groups; i++) { + mesh_groups_[i] = std::move(mesh_groups_[i + 1]); + } + mesh_groups_.resize(new_num_mesh_groups); + + // Invalidate references to removed mesh group in scene nodes, and update + // references to remaining mesh groups in scene nodes. + for (SceneNodeIndex sni(0); sni < NumNodes(); ++sni) { + SceneNode *node = GetNode(sni); + if (!node) { + return Status(Status::DRACO_ERROR, "Node is null."); + } + const MeshGroupIndex mgi = node->GetMeshGroupIndex(); + if (mgi == index) { + // TODO(vytyaz): Remove the node if possible, e.g., when node has no + // geometry, no child nodes, no skins, no lights, and no mesh group + // instance arrays. + node->SetMeshGroupIndex(kInvalidMeshGroupIndex); + } else if (mgi > index && mgi != kInvalidMeshGroupIndex) { + node->SetMeshGroupIndex(mgi - 1); + } + } + return OkStatus(); +} + +Status Scene::RemoveMaterial(int index) { + if (index < 0 || index >= material_library_.NumMaterials()) { + return Status(Status::DRACO_ERROR, "Material index is out of range."); + } + material_library_.RemoveMaterial(index); + + // Update material indices of mesh instances. + for (MeshGroupIndex mgi(0); mgi < NumMeshGroups(); ++mgi) { + MeshGroup *const mesh_group = GetMeshGroup(mgi); + for (int i = 0; i < mesh_group->NumMeshInstances(); i++) { + MeshGroup::MeshInstance &mesh_instance = mesh_group->GetMeshInstance(i); + if (mesh_instance.material_index > index) { + mesh_instance.material_index--; + } else if (mesh_instance.material_index == index) { + return Status(Status::DRACO_ERROR, "Removed material has references."); + } + } + } + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.h new file mode 100644 index 000000000..3c76ead7a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene.h @@ -0,0 +1,258 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_SCENE_H_ +#define DRACO_SCENE_SCENE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/animation/animation.h" +#include "draco/animation/skin.h" +#include "draco/mesh/mesh.h" +#include "draco/metadata/structural_metadata.h" +#include "draco/scene/instance_array.h" +#include "draco/scene/light.h" +#include "draco/scene/mesh_group.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_node.h" + +namespace draco { + +// Class used to hold all of the geometry to create a scene. A scene is +// comprised of one or more meshes, one or more scene nodes, one or more +// mesh groups, and a material library. The meshes are defined in their +// local space. A mesh group is a list of meshes. The scene nodes create +// a scene hierarchy to transform meshes in their local space into scene space. +// The material library contains all of the materials and textures used by the +// meshes in this scene. +class Scene { + public: + Scene() {} + + void Copy(const Scene &s); + + // Adds a Draco |mesh| to the scene. Returns the index to the stored mesh or + // |kInvalidMeshIndex| if the mesh is a nullptr. + MeshIndex AddMesh(std::unique_ptr mesh) { + if (mesh == nullptr) { + return kInvalidMeshIndex; + } + meshes_.push_back(std::move(mesh)); + return MeshIndex(meshes_.size() - 1); + } + + // Removes base mesh and corresponding material at |index|, removes references + // to removed base mesh and corresponding materials from mesh groups, and + // updates references to remaining base meshes in mesh groups. + Status RemoveMesh(MeshIndex index); + + // Returns the number of meshes in a scene before instancing is applied. + int NumMeshes() const { return meshes_.size(); } + + // Returns a mesh in the scene before instancing is applied. The mesh + // coordinates are local to the mesh. + Mesh &GetMesh(MeshIndex index) { return *meshes_[index]; } + const Mesh &GetMesh(MeshIndex index) const { return *meshes_[index]; } + + // Creates a mesh group and returns the index to the mesh group. + MeshGroupIndex AddMeshGroup() { + std::unique_ptr mesh(new MeshGroup()); + mesh_groups_.push_back(std::move(mesh)); + return MeshGroupIndex(mesh_groups_.size() - 1); + } + + // Removes mesh group at |index|, invalidates references to removed mesh group + // in scene nodes, and updates references to remaining mesh groups in scene + // nodes. + Status RemoveMeshGroup(MeshGroupIndex index); + + // Removes unused material at |index| and updates references to materials at + // indices greater than |index|. Returns error status when |index| is out of + // valid range and when material at |index| is used in the scene. + Status RemoveMaterial(int index); + + // Returns the number of mesh groups in a scene. + int NumMeshGroups() const { return mesh_groups_.size(); } + + // Returns a mesh group in the scene. + MeshGroup *GetMeshGroup(MeshGroupIndex index) { + return mesh_groups_[index].get(); + } + const MeshGroup *GetMeshGroup(MeshGroupIndex index) const { + return mesh_groups_[index].get(); + } + + // Creates a scene node and returns the index to the node. + SceneNodeIndex AddNode() { + std::unique_ptr node(new SceneNode()); + nodes_.push_back(std::move(node)); + return SceneNodeIndex(nodes_.size() - 1); + } + + // Returns the number of nodes in a scene. + int NumNodes() const { return nodes_.size(); } + + // Returns a node in the scene. + SceneNode *GetNode(SceneNodeIndex index) { return nodes_[index].get(); } + const SceneNode *GetNode(SceneNodeIndex index) const { + return nodes_[index].get(); + } + + // Either allocates new nodes or removes existing nodes that are beyond + // |num_nodes|. + void ResizeNodes(int num_nodes) { + const size_t old_num_nodes = nodes_.size(); + nodes_.resize(num_nodes); + for (SceneNodeIndex i(old_num_nodes); i < num_nodes; ++i) { + nodes_[i].reset(new SceneNode()); + } + } + + // Returns the number of root node indices in a scene. + int NumRootNodes() const { return root_node_indices_.size(); } + SceneNodeIndex GetRootNodeIndex(int i) const { return root_node_indices_[i]; } + const std::vector &GetRootNodeIndices() const { + return root_node_indices_; + } + void AddRootNodeIndex(SceneNodeIndex index) { + root_node_indices_.push_back(index); + } + void SetRootNodeIndex(int i, SceneNodeIndex index) { + root_node_indices_[i] = index; + } + void RemoveAllRootNodeIndices() { root_node_indices_.clear(); } + + const MaterialLibrary &GetMaterialLibrary() const { + return material_library_; + } + MaterialLibrary &GetMaterialLibrary() { return material_library_; } + + // Library that contains non-material textures. + const TextureLibrary &GetNonMaterialTextureLibrary() const { + return non_material_texture_library_; + } + TextureLibrary &GetNonMaterialTextureLibrary() { + return non_material_texture_library_; + } + + // Structural metadata. + const StructuralMetadata &GetStructuralMetadata() const { + return structural_metadata_; + } + StructuralMetadata &GetStructuralMetadata() { return structural_metadata_; } + + // Creates an animation and returns the index to the animation. + AnimationIndex AddAnimation() { + std::unique_ptr animation(new Animation()); + animations_.push_back(std::move(animation)); + return AnimationIndex(animations_.size() - 1); + } + + // Returns the number of animations in a scene. + int NumAnimations() const { return animations_.size(); } + + // Returns an animation in the scene. + Animation *GetAnimation(AnimationIndex index) { + return animations_[index].get(); + } + const Animation *GetAnimation(AnimationIndex index) const { + return animations_[index].get(); + } + + // Creates a skin and returns the index to the skin. + SkinIndex AddSkin() { + std::unique_ptr skin(new Skin()); + skins_.push_back(std::move(skin)); + return SkinIndex(skins_.size() - 1); + } + + // Returns the number of skins in a scene. + int NumSkins() const { return skins_.size(); } + + // Returns a skin in the scene. + Skin *GetSkin(SkinIndex index) { return skins_[index].get(); } + const Skin *GetSkin(SkinIndex index) const { return skins_[index].get(); } + + // Creates a light and returns the index to the light. + LightIndex AddLight() { + std::unique_ptr light(new Light()); + lights_.push_back(std::move(light)); + return LightIndex(lights_.size() - 1); + } + + // Returns the number of lights in a scene. + int NumLights() const { return lights_.size(); } + + // Returns a light in the scene. + Light *GetLight(LightIndex index) { return lights_[index].get(); } + const Light *GetLight(LightIndex index) const { return lights_[index].get(); } + + // Creates a mesh group instance array and returns the index to it. This array + // is used for storing the attributes of the EXT_mesh_gpu_instancing glTF + // extension. + InstanceArrayIndex AddInstanceArray() { + std::unique_ptr array(new InstanceArray()); + instance_arrays_.push_back(std::move(array)); + return InstanceArrayIndex(instance_arrays_.size() - 1); + } + + // Returns the number of mesh group instance arrays in a scene. + int NumInstanceArrays() const { return instance_arrays_.size(); } + + // Returns a mesh group instance array in the scene. + InstanceArray *GetInstanceArray(InstanceArrayIndex index) { + return instance_arrays_[index].get(); + } + const InstanceArray *GetInstanceArray(InstanceArrayIndex index) const { + return instance_arrays_[index].get(); + } + + private: + IndexTypeVector> meshes_; + IndexTypeVector> mesh_groups_; + IndexTypeVector> nodes_; + std::vector root_node_indices_; + IndexTypeVector> animations_; + IndexTypeVector> skins_; + + // The lights will be written to the output scene but not used for internal + // rendering in Draco, e.g, while computing distortion metric. + IndexTypeVector> lights_; + + // The mesh group instance array information will be written to the output + // scene but not processed by Draco simplifier modules. + IndexTypeVector> + instance_arrays_; + + // Materials used by this scene. + MaterialLibrary material_library_; + + // Texture library for storing non-material textures used by this scene, e.g., + // textures containing mesh feature IDs of EXT_mesh_features glTF extension. + // Note that scene meshes contain pointers to non-material textures. It is + // responsibility of class user to update these pointers when updating the + // textures. See Scene::Copy() for example. + TextureLibrary non_material_texture_library_; + + // Structural metadata defined by the EXT_structural_metadata glTF extension. + StructuralMetadata structural_metadata_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.cc new file mode 100644 index 000000000..7d0663e08 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.cc @@ -0,0 +1,109 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_are_equivalent.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/mesh/mesh_are_equivalent.h" + +namespace draco { + +bool SceneAreEquivalent::operator()(const Scene &scene0, const Scene &scene1) { + // Check scene component sizes. + if (scene0.NumAnimations() != scene1.NumAnimations()) { + return false; + } + if (scene0.NumMeshGroups() != scene1.NumMeshGroups()) { + return false; + } + if (scene0.NumSkins() != scene1.NumSkins()) { + return false; + } + + // Check equivalence of each mesh. + if (scene0.NumMeshes() != scene1.NumMeshes()) { + return false; + } + for (MeshIndex i(0); i < scene0.NumMeshes(); i++) { + if (!AreEquivalent(scene0.GetMesh(i), scene1.GetMesh(i))) { + return false; + } + } + + // Check eqiuvalence of each node. + if (scene0.NumNodes() != scene1.NumNodes()) { + return false; + } + for (SceneNodeIndex i(0); i < scene0.NumNodes(); i++) { + if (!AreEquivalent(*scene0.GetNode(i), *scene1.GetNode(i))) { + return false; + } + } + + // Check non-material texture library sizes. + if (scene0.GetNonMaterialTextureLibrary().NumTextures() != + scene1.GetNonMaterialTextureLibrary().NumTextures()) { + return false; + } + + // TODO(vytyaz): Check remaining scene properties like animations and skins. + return true; +} + +bool SceneAreEquivalent::AreEquivalent(const Mesh &mesh0, const Mesh &mesh1) { + MeshAreEquivalent eq; + return eq(mesh0, mesh1); +} + +bool SceneAreEquivalent::AreEquivalent(const SceneNode &node0, + const SceneNode &node1) { + // Check equivalence of node indices. + if (node0.GetMeshGroupIndex() != node1.GetMeshGroupIndex()) { + return false; + } + if (node0.GetSkinIndex() != node1.GetSkinIndex()) { + return false; + } + + // Check equivalence of node transformations. + if (node0.GetTrsMatrix().ComputeTransformationMatrix() != + node1.GetTrsMatrix().ComputeTransformationMatrix()) { + return false; + } + + // Check equivalence of node children. + if (node0.NumChildren() != node1.NumChildren()) { + return false; + } + for (int i = 0; i < node0.NumChildren(); i++) { + if (node0.Child(i) != node1.Child(i)) { + return false; + } + } + + // Check equivalence of node parents. + if (node0.NumParents() != node1.NumParents()) { + return false; + } + for (int i = 0; i < node0.NumParents(); i++) { + if (node0.Parent(i) != node1.Parent(i)) { + return false; + } + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.h new file mode 100644 index 000000000..b309c0338 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent.h @@ -0,0 +1,42 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ +#define DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" + +namespace draco { + +// A functor to compare two scenes for equivalency up to permutation of mesh +// vertices. +class SceneAreEquivalent { + public: + // Returns true if both scenes are equivalent up to permutation of + // the internal order of mesh vertices. This includes all attributes. + bool operator()(const Scene &scene0, const Scene &scene1); + + private: + static bool AreEquivalent(const Mesh &mesh0, const Mesh &mesh1); + static bool AreEquivalent(const SceneNode &node0, const SceneNode &node1); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc new file mode 100644 index 000000000..3a9edc6c3 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc @@ -0,0 +1,86 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_are_equivalent.h" + +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/scene_io.h" +#include "draco/scene/scene.h" + +namespace draco { + +#ifdef DRACO_TRANSCODER_SUPPORTED +class SceneAreEquivalentTest : public ::testing::Test {}; + +TEST_F(SceneAreEquivalentTest, TestOnIndenticalScenes) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(ReadSceneFromTestFile(file_name)); + ASSERT_NE(scene, nullptr) << "Failed to load test scene: " << file_name; + + // Add mesh feature ID set to a scene mesh. + std::unique_ptr mesh_features(new MeshFeatures()); + scene->GetMesh(MeshIndex(2)).AddMeshFeatures(std::move(mesh_features)); + + SceneAreEquivalent equiv; + ASSERT_TRUE(equiv(*scene, *scene)); +} + +TEST_F(SceneAreEquivalentTest, TestOnDifferentScenes) { + const std::string file_name0 = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::string file_name1 = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene0(ReadSceneFromTestFile(file_name0)); + const std::unique_ptr scene1(ReadSceneFromTestFile(file_name1)); + ASSERT_NE(scene0, nullptr) << "Failed to load test scene: " << file_name0; + ASSERT_NE(scene1, nullptr) << "Failed to load test scene: " << file_name1; + SceneAreEquivalent equiv; + ASSERT_FALSE(equiv(*scene0, *scene1)); +} + +TEST_F(SceneAreEquivalentTest, TestMeshFeatures) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene0(ReadSceneFromTestFile(file_name)); + const std::unique_ptr scene1(ReadSceneFromTestFile(file_name)); + ASSERT_NE(scene0, nullptr); + ASSERT_NE(scene1, nullptr); + + // Add identical mesh feature ID sets to mesh at index 0. + Mesh &mesh0 = scene0->GetMesh(MeshIndex(0)); + Mesh &mesh1 = scene1->GetMesh(MeshIndex(0)); + mesh0.AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + mesh1.AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + + // Empty feature sets should match. + SceneAreEquivalent equiv; + ASSERT_TRUE(equiv(*scene0, *scene1)); + + // Make mesh features different and check that the meshes are not equivalent. + mesh0.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(5); + mesh1.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(6); + ASSERT_FALSE(equiv(*scene0, *scene1)); + + // Make mesh features identical and check that the meshes are equivalent. + mesh0.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + mesh1.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + ASSERT_TRUE(equiv(*scene0, *scene1)); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace draco diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_indices.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_indices.h new file mode 100644 index 000000000..7b57e3e4a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_indices.h @@ -0,0 +1,72 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_SCENE_SCENE_INDICES_H_ +#define DRACO_SCENE_SCENE_INDICES_H_ + +#include + +#include + +#include "draco/core/draco_index_type.h" + +namespace draco { + +// Index of a mesh in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshIndex) + +// Index of a mesh instance in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshInstanceIndex) + +// Index of a mesh group in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshGroupIndex) + +// Index of a node in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, SceneNodeIndex) + +// Index of an animation in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, AnimationIndex) + +// Index of a skin in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, SkinIndex) + +// Index of a light in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, LightIndex) + +// Index of a mesh group GPU instancing in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, InstanceArrayIndex) + +// Constants denoting invalid indices. +static constexpr MeshIndex kInvalidMeshIndex( + std::numeric_limits::max()); +static constexpr MeshInstanceIndex kInvalidMeshInstanceIndex( + std::numeric_limits::max()); +static constexpr MeshGroupIndex kInvalidMeshGroupIndex( + std::numeric_limits::max()); +static constexpr SceneNodeIndex kInvalidSceneNodeIndex( + std::numeric_limits::max()); +static constexpr AnimationIndex kInvalidAnimationIndex( + std::numeric_limits::max()); +static constexpr SkinIndex kInvalidSkinIndex( + std::numeric_limits::max()); +static constexpr LightIndex kInvalidLightIndex( + std::numeric_limits::max()); +static constexpr InstanceArrayIndex kInvalidInstanceArrayIndex( + std::numeric_limits::max()); + +} // namespace draco + +#endif // DRACO_SCENE_SCENE_INDICES_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_node.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_node.h new file mode 100644 index 000000000..6cfe04e2e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_node.h @@ -0,0 +1,105 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_SCENE_SCENE_NODE_H_ +#define DRACO_SCENE_SCENE_NODE_H_ + +#include "draco/scene/scene_indices.h" +#include "draco/scene/trs_matrix.h" + +namespace draco { + +// This class is used to create a scene hierarchy from meshes in their local +// space transformed into scene space. +class SceneNode { + public: + SceneNode() + : mesh_group_index_(-1), + skin_index_(-1), + light_index_(-1), + instance_array_index_(-1) {} + + void Copy(const SceneNode &sn) { + name_ = sn.name_; + trs_matrix_.Copy(sn.trs_matrix_); + mesh_group_index_ = sn.mesh_group_index_; + skin_index_ = sn.skin_index_; + parents_ = sn.parents_; + children_ = sn.children_; + light_index_ = sn.light_index_; + instance_array_index_ = sn.instance_array_index_; + } + + // Sets a name. + void SetName(const std::string &name) { name_ = name; } + + // Returns the name. + const std::string &GetName() const { return name_; } + + // Set transformation from mesh local space to scene space. + void SetTrsMatrix(const TrsMatrix &trsm) { trs_matrix_.Copy(trsm); } + const TrsMatrix &GetTrsMatrix() const { return trs_matrix_; } + + // Set the index to the mesh group in the scene. + void SetMeshGroupIndex(MeshGroupIndex index) { mesh_group_index_ = index; } + MeshGroupIndex GetMeshGroupIndex() const { return mesh_group_index_; } + + // Set the index to the skin in the scene. + void SetSkinIndex(SkinIndex index) { skin_index_ = index; } + SkinIndex GetSkinIndex() const { return skin_index_; } + + // Set the index to the light in the scene. + void SetLightIndex(LightIndex index) { light_index_ = index; } + LightIndex GetLightIndex() const { return light_index_; } + + // Set the index to the mesh group instance array in the scene. Note that + // according to EXT_mesh_gpu_instancing glTF extension there is no defined + // behavior for a node with instance array and without a mesh group. + void SetInstanceArrayIndex(InstanceArrayIndex index) { + instance_array_index_ = index; + } + InstanceArrayIndex GetInstanceArrayIndex() const { + return instance_array_index_; + } + + // Functions to set and get zero or more parent nodes of this node. + SceneNodeIndex Parent(int index) const { return parents_[index]; } + const std::vector &Parents() const { return parents_; } + void AddParentIndex(SceneNodeIndex index) { parents_.push_back(index); } + int NumParents() const { return parents_.size(); } + void RemoveAllParents() { parents_.clear(); } + + // Functions to set and get zero or more child nodes of this node. + SceneNodeIndex Child(int index) const { return children_[index]; } + const std::vector &Children() const { return children_; } + void AddChildIndex(SceneNodeIndex index) { children_.push_back(index); } + int NumChildren() const { return children_.size(); } + void RemoveAllChildren() { children_.clear(); } + + private: + std::string name_; + TrsMatrix trs_matrix_; + draco::MeshGroupIndex mesh_group_index_; + draco::SkinIndex skin_index_; + std::vector parents_; + std::vector children_; + LightIndex light_index_; + InstanceArrayIndex instance_array_index_; +}; + +} // namespace draco + +#endif // DRACO_SCENE_SCENE_NODE_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_test.cc new file mode 100644 index 000000000..d639614c7 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_test.cc @@ -0,0 +1,295 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/status.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/scene/scene_are_equivalent.h" +#include "draco/scene/scene_indices.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +// Helper method for adding mesh group GPU instancing to the milk truck scene. +draco::Status AddGpuInstancingToMilkTruck(draco::Scene *scene) { + // Create an instance and set its transformation TRS vectors. + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance_0.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + instance_0.trs.SetScale(Eigen::Vector3d(8.0, 9.0, 10.0)); + + // Create another instance. + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(Eigen::Vector3d(1.1, 2.1, 3.1)); + instance_1.trs.SetRotation(Eigen::Quaterniond(4.1, 5.1, 6.1, 7.1)); + instance_1.trs.SetScale(Eigen::Vector3d(8.1, 9.1, 10.1)); + + // Add an empty GPU instancing object to the scene. + const draco::InstanceArrayIndex index = scene->AddInstanceArray(); + draco::InstanceArray *gpu_instancing = scene->GetInstanceArray(index); + + // Add two instances to the GPU instancing object stored in the scene. + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_0)); + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_1)); + + // Assign the GPU instancing object to two mesh groups in two scene nodes. + scene->GetNode(draco::SceneNodeIndex(2))->SetInstanceArrayIndex(index); + scene->GetNode(draco::SceneNodeIndex(4))->SetInstanceArrayIndex(index); + + return draco::OkStatus(); +} + +TEST(SceneTest, TestCopy) { + // Test copying of scene data. + auto src_scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene, nullptr); + + // Add GPU instancing to the scene for testing. + DRACO_ASSERT_OK(AddGpuInstancingToMilkTruck(src_scene.get())); + ASSERT_EQ(src_scene->NumInstanceArrays(), 1); + ASSERT_EQ(src_scene->NumNodes(), 5); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(0))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(1))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(2))->GetInstanceArrayIndex(), + draco::InstanceArrayIndex(0)); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(3))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(4))->GetInstanceArrayIndex(), + draco::InstanceArrayIndex(0)); + + // Make a copy of the scene. + draco::Scene dst_scene; + dst_scene.Copy(*src_scene); + + ASSERT_EQ(src_scene->NumMeshes(), dst_scene.NumMeshes()); + ASSERT_EQ(src_scene->NumMeshGroups(), dst_scene.NumMeshGroups()); + ASSERT_EQ(src_scene->NumNodes(), dst_scene.NumNodes()); + ASSERT_EQ(src_scene->NumAnimations(), dst_scene.NumAnimations()); + ASSERT_EQ(src_scene->NumSkins(), dst_scene.NumSkins()); + ASSERT_EQ(src_scene->NumLights(), dst_scene.NumLights()); + ASSERT_EQ(src_scene->NumInstanceArrays(), dst_scene.NumInstanceArrays()); + + for (draco::MeshIndex i(0); i < src_scene->NumMeshes(); ++i) { + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(src_scene->GetMesh(i), dst_scene.GetMesh(i))); + } + for (draco::MeshGroupIndex i(0); i < src_scene->NumMeshGroups(); ++i) { + ASSERT_EQ(src_scene->GetMeshGroup(i)->NumMeshInstances(), + dst_scene.GetMeshGroup(i)->NumMeshInstances()); + for (int j = 0; j < src_scene->GetMeshGroup(i)->NumMeshInstances(); ++j) { + ASSERT_EQ(src_scene->GetMeshGroup(i)->GetMeshInstance(j).mesh_index, + dst_scene.GetMeshGroup(i)->GetMeshInstance(j).mesh_index); + ASSERT_EQ(src_scene->GetMeshGroup(i)->GetMeshInstance(j).material_index, + dst_scene.GetMeshGroup(i)->GetMeshInstance(j).material_index); + ASSERT_EQ(src_scene->GetMeshGroup(i) + ->GetMeshInstance(j) + .materials_variants_mappings.size(), + dst_scene.GetMeshGroup(i) + ->GetMeshInstance(j) + .materials_variants_mappings.size()); + } + } + for (draco::SceneNodeIndex i(0); i < src_scene->NumNodes(); ++i) { + ASSERT_EQ(src_scene->GetNode(i)->NumParents(), + dst_scene.GetNode(i)->NumParents()); + for (int j = 0; j < src_scene->GetNode(i)->NumParents(); ++j) { + ASSERT_EQ(src_scene->GetNode(i)->Parent(j), + dst_scene.GetNode(i)->Parent(j)); + } + ASSERT_EQ(src_scene->GetNode(i)->NumChildren(), + dst_scene.GetNode(i)->NumChildren()); + for (int j = 0; j < src_scene->GetNode(i)->NumChildren(); ++j) { + ASSERT_EQ(src_scene->GetNode(i)->Child(j), + dst_scene.GetNode(i)->Child(j)); + } + ASSERT_EQ(src_scene->GetNode(i)->GetMeshGroupIndex(), + dst_scene.GetNode(i)->GetMeshGroupIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetSkinIndex(), + dst_scene.GetNode(i)->GetSkinIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetLightIndex(), + dst_scene.GetNode(i)->GetLightIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetInstanceArrayIndex(), + dst_scene.GetNode(i)->GetInstanceArrayIndex()); + } +} + +TEST(SceneTest, TestRemoveMesh) { + // Test that a base mesh can be removed from scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + ASSERT_EQ(dst_scene.NumMeshes(), 4); + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(1)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(2)), + src_scene.GetMesh(draco::MeshIndex(2)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(3)), + src_scene.GetMesh(draco::MeshIndex(3)))); + + // Remove base mesh from scene. + dst_scene.RemoveMesh(draco::MeshIndex(2)); + ASSERT_EQ(dst_scene.NumMeshes(), 3); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(1)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(2)), + src_scene.GetMesh(draco::MeshIndex(3)))); + + // Remove another base mesh from scene. + dst_scene.RemoveMesh(draco::MeshIndex(1)); + ASSERT_EQ(dst_scene.NumMeshes(), 2); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(3)))); +} + +TEST(SceneTest, TestRemoveMeshGroup) { + // Test that a mesh group can be removed from scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + ASSERT_EQ(dst_scene.NumMeshGroups(), 2); + ASSERT_EQ(dst_scene.NumNodes(), 5); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::MeshGroupIndex(1)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::MeshGroupIndex(1)); + + // Remove mesh group from scene. + dst_scene.RemoveMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.NumMeshGroups(), 1); + ASSERT_EQ(dst_scene.NumNodes(), 5); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Remove another mesh group from scene. + dst_scene.RemoveMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.NumMeshGroups(), 0); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); +} + +void CheckMeshMaterials(const draco::Scene &scene, + const std::vector &expected_material_indices) { + ASSERT_EQ(scene.NumMeshes(), expected_material_indices.size()); + std::vector scene_material_indices; + for (draco::MeshGroupIndex i(0); i < scene.NumMeshGroups(); i++) { + const auto mg = scene.GetMeshGroup(i); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + scene_material_indices.push_back(mg->GetMeshInstance(mi).material_index); + } + } + ASSERT_EQ(scene_material_indices, expected_material_indices); +} + +TEST(SceneTest, TestRemoveMaterial) { + // Test that materials can be removed from a scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + ASSERT_EQ(src_scene.GetMaterialLibrary().NumMaterials(), 4); + CheckMeshMaterials(src_scene, {0, 1, 2, 3}); + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + + // Check that referenced material cannot be removed from the scene. + ASSERT_FALSE(dst_scene.RemoveMaterial(2).ok()); + + // Copy scene again, since failed material removal corrupts the scene. + dst_scene.Copy(src_scene); + + // Remove base mesh from scene. Material at index 2 becomes unreferenced. + DRACO_ASSERT_OK(dst_scene.RemoveMesh(draco::MeshIndex(2))); + ASSERT_EQ(dst_scene.GetMaterialLibrary().NumMaterials(), 4); + CheckMeshMaterials(dst_scene, {0, 1, 3}); + + // Check that unreferenced material can be removed from the scene. + DRACO_ASSERT_OK(dst_scene.RemoveMaterial(2)); + ASSERT_EQ(dst_scene.GetMaterialLibrary().NumMaterials(), 3); + CheckMeshMaterials(dst_scene, {0, 1, 2}); + + // Check that material cannot be removed when material index is out of range. + ASSERT_FALSE(dst_scene.RemoveMaterial(-1).ok()); + ASSERT_FALSE(dst_scene.RemoveMaterial(3).ok()); +} + +TEST(SceneTest, TestCopyWithStructuralMetadata) { + // Tests copying of a scene with structural metadata. + auto scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene_ptr, nullptr); + draco::Scene &scene = *scene_ptr; + + // Add structural metadata to the scene. + draco::PropertyTable::Schema schema; + schema.json.SetString("Data"); + scene.GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Copy the scene. + draco::Scene copy; + copy.Copy(scene); + + // Check that the structural metadata has been copied. + ASSERT_EQ( + copy.GetStructuralMetadata().GetPropertyTableSchema().json.GetString(), + "Data"); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.cc new file mode 100644 index 000000000..a7bf1dcb9 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.cc @@ -0,0 +1,962 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "draco/scene/scene_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/draco_index_type_vector.h" +#include "draco/core/hash_utils.h" +#include "draco/core/vector_d.h" +#include "draco/mesh/mesh_splitter.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/scene_indices.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +IndexTypeVector +SceneUtils::ComputeAllInstances(const Scene &scene) { + IndexTypeVector instances; + + // Traverse the scene assuming multiple root nodes. + const Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + + struct Node { + const SceneNodeIndex scene_node_index; + Eigen::Matrix4d transform; + }; + std::vector nodes; + nodes.reserve(scene.NumRootNodes()); + for (int i = 0; i < scene.NumRootNodes(); ++i) { + nodes.push_back({scene.GetRootNodeIndex(i), transform}); + } + + while (!nodes.empty()) { + const Node node = nodes.back(); + nodes.pop_back(); + const SceneNode &scene_node = *scene.GetNode(node.scene_node_index); + const Eigen::Matrix4d combined_transform = + node.transform * + scene_node.GetTrsMatrix().ComputeTransformationMatrix(); + + // Create instances from node meshes. + const MeshGroupIndex mesh_group_index = scene_node.GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + const MeshGroup &mesh_group = *scene.GetMeshGroup(mesh_group_index); + for (int i = 0; i < mesh_group.NumMeshInstances(); i++) { + const MeshIndex mesh_index = mesh_group.GetMeshInstance(i).mesh_index; + if (mesh_index != kInvalidMeshIndex) { + instances.push_back( + {mesh_index, node.scene_node_index, i, combined_transform}); + } + } + } + + // Traverse children nodes. + for (int i = 0; i < scene_node.NumChildren(); i++) { + nodes.push_back({scene_node.Child(i), combined_transform}); + } + } + return instances; +} + +Eigen::Matrix4d SceneUtils::ComputeGlobalNodeTransform(const Scene &scene, + SceneNodeIndex index) { + Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + while (index != kInvalidSceneNodeIndex) { + const SceneNode *const node = scene.GetNode(index); + transform = node->GetTrsMatrix().ComputeTransformationMatrix() * transform; + index = node->NumParents() == 1 ? node->Parent(0) : kInvalidSceneNodeIndex; + } + return transform; +} + +IndexTypeVector SceneUtils::NumMeshInstances( + const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + IndexTypeVector num_mesh_instances(scene.NumMeshes(), 0); + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_mesh_instances[instance.mesh_index]++; + } + return num_mesh_instances; +} + +int SceneUtils::GetMeshInstanceMaterialIndex(const Scene &scene, + const MeshInstance &instance) { + const auto *const node = scene.GetNode(instance.scene_node_index); + return scene.GetMeshGroup(node->GetMeshGroupIndex()) + ->GetMeshInstance(instance.mesh_group_mesh_index) + .material_index; +} + +int SceneUtils::NumFacesOnBaseMeshes(const Scene &scene) { + int num_faces = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + num_faces += scene.GetMesh(i).num_faces(); + } + return num_faces; +} + +int SceneUtils::NumFacesOnInstancedMeshes(const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + int num_faces = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_faces += scene.GetMesh(instance.mesh_index).num_faces(); + } + return num_faces; +} + +int SceneUtils::NumPointsOnBaseMeshes(const Scene &scene) { + int num_points = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + num_points += scene.GetMesh(i).num_points(); + } + return num_points; +} + +int SceneUtils::NumPointsOnInstancedMeshes(const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + int num_points = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_points += scene.GetMesh(instance.mesh_index).num_points(); + } + return num_points; +} + +int SceneUtils::NumAttEntriesOnBaseMeshes(const Scene &scene, + GeometryAttribute::Type att_type) { + int num_att_entries = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + const Mesh &mesh = scene.GetMesh(i); + const PointAttribute *att = mesh.GetNamedAttribute(att_type); + if (att != nullptr) { + num_att_entries += att->size(); + } + } + return num_att_entries; +} + +int SceneUtils::NumAttEntriesOnInstancedMeshes( + const Scene &scene, GeometryAttribute::Type att_type) { + const auto instances = ComputeAllInstances(scene); + int num_att_entries = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + const PointAttribute *att = mesh.GetNamedAttribute(att_type); + if (att != nullptr) { + num_att_entries += att->size(); + } + } + return num_att_entries; +} + +BoundingBox SceneUtils::ComputeBoundingBox(const Scene &scene) { + // Compute bounding box that includes all scene mesh instances. + const auto instances = ComputeAllInstances(scene); + BoundingBox scene_bbox; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + const BoundingBox mesh_bbox = + ComputeMeshInstanceBoundingBox(scene, instance); + scene_bbox.Update(mesh_bbox); + } + return scene_bbox; +} + +BoundingBox SceneUtils::ComputeMeshInstanceBoundingBox( + const Scene &scene, const MeshInstance &instance) { + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + BoundingBox mesh_bbox; + auto pc_att = mesh.GetNamedAttribute(GeometryAttribute::POSITION); + Eigen::Vector4d position; + position[3] = 1.0; + for (AttributeValueIndex i(0); i < pc_att->size(); ++i) { + pc_att->ConvertValue(i, &position[0]); + const Eigen::Vector4d transformed = instance.transform * position; + mesh_bbox.Update({static_cast(transformed[0]), + static_cast(transformed[1]), + static_cast(transformed[2])}); + } + return mesh_bbox; +} + +namespace { + +// Updates texture pointers in mesh features of |mesh| to texture pointers +// stored in |new_texture_library|. |texture_to_index_map| stores texture +// indices of the old texture pointers within |mesh|. +void UpdateMeshFeaturesTexturesOnMesh( + const std::unordered_map &texture_to_index_map, + TextureLibrary *new_texture_library, Mesh *mesh) { + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + mesh->UpdateMeshFeaturesTexturePointer( + texture_to_index_map, new_texture_library, &mesh->GetMeshFeatures(mfi)); + } +} + +} // namespace + +StatusOr> SceneUtils::MeshToScene( + std::unique_ptr mesh) { + const size_t num_mesh_materials = mesh->GetMaterialLibrary().NumMaterials(); + std::unique_ptr scene(new Scene()); + if (num_mesh_materials > 0) { + scene->GetMaterialLibrary().Copy(mesh->GetMaterialLibrary()); + mesh->GetMaterialLibrary().Clear(); + } else { + // Create a default material for the scene. + scene->GetMaterialLibrary().MutableMaterial(0); + } + + // Copy mesh feature textures. + scene->GetNonMaterialTextureLibrary().Copy( + mesh->GetNonMaterialTextureLibrary()); + + const auto old_texture_to_index_map = + mesh->GetNonMaterialTextureLibrary().ComputeTextureToIndexMap(); + + const SceneNodeIndex scene_node_index = scene->AddNode(); + SceneNode *const scene_node = scene->GetNode(scene_node_index); + const MeshGroupIndex mesh_group_index = scene->AddMeshGroup(); + MeshGroup *const mesh_group = scene->GetMeshGroup(mesh_group_index); + + if (num_mesh_materials <= 1) { + const MeshIndex mesh_index = scene->AddMesh(std::move(mesh)); + if (mesh_index == kInvalidMeshIndex) { + // No idea whether this can happen. It's not covered by any unit test. + return Status(Status::DRACO_ERROR, "Could not add Draco mesh to scene."); + } + mesh_group->AddMeshInstance({mesh_index, 0, {}}); + + UpdateMeshFeaturesTexturesOnMesh(old_texture_to_index_map, + &scene->GetNonMaterialTextureLibrary(), + &scene->GetMesh(mesh_index)); + + } else { + const int32_t mat_att_id = + mesh->GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (mat_att_id == -1) { + // Probably dead code, not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Internal error in MeshToScene: " + "GetNamedAttributeId(MATERIAL) returned -1"); + } + const PointAttribute *const mat_att = + mesh->GetNamedAttribute(GeometryAttribute::MATERIAL); + if (mat_att == nullptr) { + // Probably dead code, not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Internal error in MeshToScene: " + "GetNamedAttribute(MATERIAL) returned nullptr"); + } + + MeshSplitter splitter; + DRACO_ASSIGN_OR_RETURN(MeshSplitter::MeshVector split_meshes, + splitter.SplitMesh(*mesh, mat_att_id)); + // Note: cannot clear mesh here, since mat_att points into it. + for (size_t i = 0; i < split_meshes.size(); ++i) { + if (split_meshes[i] == nullptr) { + // Probably dead code, not covered by any unit test. + continue; + } + const MeshIndex mesh_index = scene->AddMesh(std::move(split_meshes[i])); + if (mesh_index == kInvalidMeshIndex) { + // No idea whether this can happen. It's not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Could not add Draco mesh to scene."); + } + + int material_index = 0; + mat_att->GetValue(AttributeValueIndex(i), &material_index); + mesh_group->AddMeshInstance({mesh_index, material_index, {}}); + + // Copy over mesh features that were associated with the |material_index|. + Mesh &scene_mesh = scene->GetMesh(mesh_index); + Mesh::CopyMeshFeaturesForMaterial(*mesh, &scene_mesh, material_index); + UpdateMeshFeaturesTexturesOnMesh(old_texture_to_index_map, + &scene->GetNonMaterialTextureLibrary(), + &scene_mesh); + } + } + + scene_node->SetMeshGroupIndex(mesh_group_index); + scene->AddRootNodeIndex(scene_node_index); + return scene; +} + +void SceneUtils::PrintInfo(const Scene &input, const Scene &simplified, + bool verbose) { + struct Printer { + Printer(const Scene &input, const Scene &simplified) + : input(input), simplified(simplified), print_instanced_info(false) { + // Info about the instanced meshes is printed if some of the meshes have + // multiple instances and also if the number of base meshes has changed. + auto input_instances = SceneUtils::NumMeshInstances(input); + auto simplified_instances = SceneUtils::NumMeshInstances(simplified); + if (input_instances.size() != simplified_instances.size()) { + print_instanced_info = true; + return; + } + for (MeshIndex i(0); i < input_instances.size(); i++) { + if (input_instances[i] != 1 || simplified_instances[i] != 1) { + print_instanced_info = true; + return; + } + } + } + + void PrintInfoHeader() const { + printf("\n"); + printf("%21s | geometry: base", ""); + if (print_instanced_info) { + printf(" instanced"); + } + printf("\n"); + } + + void PrintInfoRow(const std::string &label, int count_input_base, + int count_input_instanced, int count_simplified_base, + int count_simplified_instanced) const { + // Do not clutter the printout with empty info. + if (count_input_base == 0 && count_input_instanced == 0) { + return; + } + printf(" ----------------------------------------------"); + if (print_instanced_info) { + printf("-------------"); + } + printf("\n"); + printf("%21s | input: %12d", label.c_str(), count_input_base); + if (print_instanced_info) { + printf(" %12d", count_input_instanced); + } + printf("\n"); + printf("%21s | simplified: %12d", "", count_simplified_base); + if (print_instanced_info) { + printf(" %12d", count_simplified_instanced); + } + printf("\n"); + } + + void PrintAttInfoRow(const std::string &label, const draco::Scene &input, + const draco::Scene &simplified, + draco::GeometryAttribute::Type att_type) const { + PrintInfoRow(label, NumAttEntriesOnBaseMeshes(input, att_type), + NumAttEntriesOnInstancedMeshes(input, att_type), + NumAttEntriesOnBaseMeshes(simplified, att_type), + NumAttEntriesOnInstancedMeshes(simplified, att_type)); + } + + const Scene &input; + const Scene &simplified; + bool print_instanced_info; + }; + + // Print information about input and simplified scenes. + const Printer printer(input, simplified); + printer.PrintInfoHeader(); + if (verbose) { + const int num_meshes_input_base = input.NumMeshes(); + const int num_meshes_simplified_base = simplified.NumMeshes(); + const int num_meshes_input_instanced = ComputeAllInstances(input).size(); + const int num_meshes_simplified_instanced = + ComputeAllInstances(simplified).size(); + printer.PrintInfoRow("Number of meshes", num_meshes_input_base, + num_meshes_input_instanced, num_meshes_simplified_base, + num_meshes_simplified_instanced); + } + printer.PrintInfoRow("Number of faces", NumFacesOnBaseMeshes(input), + NumFacesOnInstancedMeshes(input), + NumFacesOnBaseMeshes(simplified), + NumFacesOnInstancedMeshes(simplified)); + if (verbose) { + printer.PrintInfoRow("Number of points", NumPointsOnBaseMeshes(input), + NumPointsOnInstancedMeshes(input), + NumPointsOnBaseMeshes(simplified), + NumPointsOnInstancedMeshes(simplified)); + printer.PrintAttInfoRow("Number of positions", input, simplified, + draco::GeometryAttribute::POSITION); + printer.PrintAttInfoRow("Number of normals", input, simplified, + draco::GeometryAttribute::NORMAL); + printer.PrintAttInfoRow("Number of colors", input, simplified, + draco::GeometryAttribute::COLOR); + printer.PrintInfoRow("Number of materials", + input.GetMaterialLibrary().NumMaterials(), + simplified.GetMaterialLibrary().NumMaterials(), + input.GetMaterialLibrary().NumMaterials(), + simplified.GetMaterialLibrary().NumMaterials()); + } +} + +StatusOr> SceneUtils::InstantiateMesh( + const Scene &scene, const MeshInstance &instance) { + // Check if the |scene| has base mesh corresponding to mesh |instance|. + if (scene.NumMeshes() <= instance.mesh_index.value()) { + Status(Status::DRACO_ERROR, "Scene has no corresponding base mesh."); + } + + // Check that mesh has valid positions. + const Mesh &base_mesh = scene.GetMesh(instance.mesh_index); + const int32_t pos_id = + base_mesh.GetNamedAttributeId(GeometryAttribute::POSITION); + const PointAttribute *const pos_att = base_mesh.attribute(pos_id); + if (pos_att == nullptr) { + return Status(Status::DRACO_ERROR, "Mesh has no positions."); + } + if (pos_att->data_type() != DT_FLOAT32 || pos_att->num_components() != 3) { + return Status(Status::DRACO_ERROR, "Mesh has invalid positions."); + } + + // Copy the base mesh from |scene|. + std::unique_ptr mesh(new Mesh()); + mesh->Copy(base_mesh); + + // Apply transformation to mesh unless transformation is identity. + if (instance.transform != Eigen::Matrix4d::Identity()) { + MeshUtils::TransformMesh(instance.transform, mesh.get()); + } + return mesh; +} + +namespace { + +// Helper class for deleting unused nodes from the scene. +class SceneUnusedNodeRemover { + public: + // Removes unused nodes from the |scene|. + void RemoveUnusedNodes(Scene *scene) { + // Finds all unused nodes and initializes |node_map_| that maps old node + // indices to new node indices. + const int num_unused_nodes = FindUnusedNodes(*scene); + if (num_unused_nodes == 0) { + return; // All nodes are used. + } + + // Update indices of all scene elements accounting for nodes that are going + // to be removed from the scene. + UpdateNodeIndices(scene); + RemoveUnusedNodesFromScene(scene); + } + + private: + // Returns the number of unused nodes. + int FindUnusedNodes(const Scene &scene) { + // First all nodes are considered unused (mapped to invalid index). + // Initially if a node is used, we just map it to its own index. The final + // mapping will be updated once we know all used nodes. + node_map_.resize(scene.NumNodes(), kInvalidSceneNodeIndex); + for (SceneNodeIndex sni(0); sni < scene.NumNodes(); ++sni) { + // If the scene node has a valid mesh group, mark it as used. + if (scene.GetNode(sni)->GetMeshGroupIndex() != kInvalidMeshGroupIndex) { + node_map_[sni] = sni; + } + } + + // Preserve nodes used by animations. + for (AnimationIndex i(0); i < scene.NumAnimations(); i++) { + const Animation &animation = *scene.GetAnimation(i); + for (int channel_i = 0; channel_i < animation.NumChannels(); + channel_i++) { + const SceneNodeIndex node_index( + animation.GetChannel(channel_i)->target_index); + node_map_[node_index] = node_index; + } + } + for (SkinIndex i(0); i < scene.NumSkins(); i++) { + const Skin &skin = *scene.GetSkin(i); + for (int j = 0; j < skin.NumJoints(); j++) { + const SceneNodeIndex node_index = skin.GetJoint(j); + node_map_[node_index] = node_index; + } + const SceneNodeIndex root_index = skin.GetJointRoot(); + if (root_index != kInvalidSceneNodeIndex) { + node_map_[root_index] = root_index; + } + } + + // Ensure that "unused" nodes with used child nodes are marked as used + // (a node can't be deleted as long as it has a used child node). + for (int r = 0; r < scene.NumRootNodes(); ++r) { + UpdateUsedNodesFromSceneGraph(scene, scene.GetRootNodeIndex(r)); + } + + // All used / unused nodes are known. Find new indices for all scene nodes. + int num_valid_nodes = 0; + for (SceneNodeIndex sni(0); sni < scene.NumNodes(); ++sni) { + if (node_map_[sni] != kInvalidSceneNodeIndex) { + node_map_[sni] = SceneNodeIndex(num_valid_nodes++); + } + } + // Return the number of nodes that were unused. + return scene.NumNodes() - num_valid_nodes; + } + + // Recursively traverse node |sni| and mark it as used as long as it has a + // used child node. The function returns true when |sni| is a used node. + bool UpdateUsedNodesFromSceneGraph(const Scene &scene, SceneNodeIndex sni) { + const auto &node = scene.GetNode(sni); + bool is_any_child_node_used = false; + for (int c = 0; c < node->NumChildren(); ++c) { + const SceneNodeIndex cni = node->Child(c); + // Check if the child node is used. + const bool is_c_used = UpdateUsedNodesFromSceneGraph(scene, cni); + if (is_c_used) { + is_any_child_node_used = true; + } + } + if (is_any_child_node_used) { + // The node must be used even if it was previously marked as unused. + node_map_[sni] = sni; + } + // Returns whether this node is used or not. + return node_map_[sni] != kInvalidSceneNodeIndex; + } + + // Remaps existing node indices at various scene elements to new node indices + // defined by |node_map_|. + void UpdateNodeIndices(Scene *scene) const { + // Update node indices on child / parent nodes. + std::vector indices; + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + indices = scene->GetNode(sni)->Children(); + scene->GetNode(sni)->RemoveAllChildren(); + for (int j = 0; j < indices.size(); ++j) { + const SceneNodeIndex new_sni = node_map_[indices[j]]; + if (new_sni != kInvalidSceneNodeIndex) { + scene->GetNode(sni)->AddChildIndex(new_sni); + } + } + indices = scene->GetNode(sni)->Parents(); + scene->GetNode(sni)->RemoveAllParents(); + for (int j = 0; j < indices.size(); ++j) { + const SceneNodeIndex new_sni = node_map_[indices[j]]; + if (new_sni != kInvalidSceneNodeIndex) { + scene->GetNode(sni)->AddParentIndex(new_sni); + } + } + } + + // Update root node indices. + indices = scene->GetRootNodeIndices(); + scene->RemoveAllRootNodeIndices(); + for (int ri = 0; ri < indices.size(); ++ri) { + const SceneNodeIndex new_rni = node_map_[indices[ri]]; + if (new_rni != kInvalidSceneNodeIndex) { + scene->AddRootNodeIndex(new_rni); + } + } + + // Update node indices used by animations. + for (AnimationIndex i(0); i < scene->NumAnimations(); i++) { + Animation &animation = *scene->GetAnimation(i); + for (int i = 0; i < animation.NumChannels(); i++) { + const SceneNodeIndex node_index(animation.GetChannel(i)->target_index); + animation.GetChannel(i)->target_index = node_map_[node_index].value(); + } + } + for (SkinIndex i(0); i < scene->NumSkins(); i++) { + Skin &skin = *scene->GetSkin(i); + for (int j = 0; j < skin.NumJoints(); j++) { + const SceneNodeIndex node_index = skin.GetJoint(j); + skin.GetJoint(j) = node_map_[node_index]; + } + const SceneNodeIndex root_index = skin.GetJointRoot(); + if (root_index != kInvalidSceneNodeIndex) { + skin.SetJointRoot(node_map_[root_index]); + } + } + } + + // Removes all unused nodes from the scene. + void RemoveUnusedNodesFromScene(Scene *scene) const { + int num_valid_nodes = 0; + // Copy over nodes to their new position in the nodes array. + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const SceneNodeIndex new_sni = node_map_[sni]; + if (new_sni == kInvalidSceneNodeIndex) { + continue; + } + num_valid_nodes++; + if (sni != new_sni) { + // Copy over the |sni| node to the new location (|new_sni| is lower than + // |sni|). + scene->GetNode(new_sni)->Copy(*scene->GetNode(sni)); + } + } + // Resize the nodes in the scene to account for the unused ones. This will + // delete all unused nodes. + scene->ResizeNodes(num_valid_nodes); + } + + IndexTypeVector node_map_; +}; + +} // namespace + +void SceneUtils::Cleanup(Scene *scene) { Cleanup(scene, CleanupOptions()); } + +void SceneUtils::Cleanup(Scene *scene, const CleanupOptions &options) { + // Remove invalid mesh indices from mesh groups. + if (options.remove_invalid_mesh_instances) { + for (MeshGroupIndex i(0); i < scene->NumMeshGroups(); i++) { + scene->GetMeshGroup(i)->RemoveMeshInstances(kInvalidMeshIndex); + } + } + + // Find references to mesh groups. + std::vector is_mesh_group_referenced(scene->NumMeshGroups(), false); + for (SceneNodeIndex i(0); i < scene->NumNodes(); i++) { + const SceneNode &node = *scene->GetNode(i); + const MeshGroupIndex mesh_group_index = node.GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + is_mesh_group_referenced[mesh_group_index.value()] = true; + } + } + + // Find references to base meshes from referenced mesh groups and find mesh + // groups that have no valid references to base meshes. + std::vector is_base_mesh_referenced(scene->NumMeshes(), false); + std::vector is_mesh_group_empty(scene->NumMeshGroups(), false); + for (MeshGroupIndex i(0); i < scene->NumMeshGroups(); i++) { + if (!is_mesh_group_referenced[i.value()]) { + continue; + } + const MeshGroup &mesh_group = *scene->GetMeshGroup(i); + bool mesh_group_is_empty = true; + for (int j = 0; j < mesh_group.NumMeshInstances(); j++) { + const MeshIndex mesh_index = mesh_group.GetMeshInstance(j).mesh_index; + mesh_group_is_empty = false; + is_base_mesh_referenced[mesh_index.value()] = true; + } + if (mesh_group_is_empty) { + is_mesh_group_empty[i.value()] = true; + } + } + + if (options.remove_unused_meshes) { + // Remove base meshes with no references to them. + for (int i = scene->NumMeshes() - 1; i >= 0; i--) { + const MeshIndex mi(i); + if (!is_base_mesh_referenced[mi.value()]) { + scene->RemoveMesh(mi); + } + } + } + + if (options.remove_unused_mesh_groups) { + // Remove empty mesh groups with no geometry or no references to them. + for (int i = scene->NumMeshGroups() - 1; i >= 0; i--) { + const MeshGroupIndex mgi(i); + if (is_mesh_group_empty[mgi.value()] || + !is_mesh_group_referenced[mgi.value()]) { + scene->RemoveMeshGroup(mgi); + } + } + } + + // Find materials that reference a texture. + MaterialLibrary &material_library = scene->GetMaterialLibrary(); + std::vector materials_with_textures(material_library.NumMaterials(), + false); + for (int i = 0; i < material_library.NumMaterials(); ++i) { + if (material_library.GetMaterial(i)->NumTextureMaps() > 0) { + materials_with_textures[i] = true; + } + } + + // Maps material index to a set of meshes that use that material. + std::vector> material_meshes( + material_library.NumMaterials()); + + // Maps mesh index to a set of materials used by that mesh. + IndexTypeVector> mesh_materials( + scene->NumMeshes()); + + // Maps mesh index to a set of tex coord indices referenced by materials. + IndexTypeVector> tex_coord_referenced( + scene->NumMeshes()); + + // Populate the maps that will be used to remove unused texture coordinates. + for (int mgi = 0; mgi < scene->NumMeshGroups(); ++mgi) { + const MeshGroup *const mesh_group = + scene->GetMeshGroup(MeshGroupIndex(mgi)); + for (int mi = 0; mi < mesh_group->NumMeshInstances(); ++mi) { + const MeshIndex mesh_index = mesh_group->GetMeshInstance(mi).mesh_index; + const int material_index = mesh_group->GetMeshInstance(mi).material_index; + if (material_index == -1) { + continue; + } + + // Populate mesh-material mapping. + material_meshes[material_index].insert(mesh_index); + mesh_materials[mesh_index].insert(material_index); + + // Populate texture coordinate indices referenced by material textures. + const auto material = material_library.GetMaterial(material_index); + for (int i = 0; i < material->NumTextureMaps(); i++) { + const TextureMap *const texture_map = material->GetTextureMapByIndex(i); + const int tex_coord_index = texture_map->tex_coord_index(); + tex_coord_referenced[mesh_index].insert(tex_coord_index); + } + } + } + + // From each mesh, remove texture coordinate attributes that are not + // referenced by any materials and decrement texture coordinate indices in + // texture maps of the mesh materials accordingly. + if (options.remove_unused_tex_coords) { + for (MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + // Do not remove unreferenced texture coordinates when the mesh materials + // are used by any other meshes to avoid corrupting those other meshes. + // TODO(vytyaz): Consider removing this limitation. + bool remove_tex_coord = true; + for (const int material_index : mesh_materials[mi]) { + if (material_meshes[material_index].size() != 1) { + // Materials of this mesh are used by other meshes. + remove_tex_coord = false; + break; + } + } + if (!remove_tex_coord) { + continue; + } + + // Remove unreferenced texture coordinate sets from this mesh. + Mesh &mesh = scene->GetMesh(mi); + const int tex_coord_count = + mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD); + for (int tci = tex_coord_count - 1; tci >= 0; tci--) { + if (tex_coord_referenced[mi].count(tci) != 0) { + // Texture coordinate set is referenced. + continue; + } + mesh.DeleteAttribute( + mesh.GetNamedAttributeId(GeometryAttribute::TEX_COORD, tci)); + + // Decrement texture coordinate indices in all materials of this mesh. + for (const int material_index : mesh_materials[mi]) { + auto material = material_library.MutableMaterial(material_index); + for (int i = 0; i < material->NumTextureMaps(); i++) { + auto texture_map = material->GetTextureMapByIndex(i); + // Decrement the indices that are greater than the removed index. + if (texture_map->tex_coord_index() > tci) { + texture_map->SetProperties(texture_map->type(), + texture_map->tex_coord_index() - 1); + } + } + } + } + } + } + + if (options.remove_unused_materials) { + // Remove materials that are not used by any mesh. + for (int i = material_library.NumMaterials() - 1; i >= 0; --i) { + if (material_meshes[i].empty()) { + // Material |i| is not used. + scene->RemoveMaterial(i); + } + } + } + + if (options.remove_unused_nodes) { + SceneUnusedNodeRemover node_remover; + node_remover.RemoveUnusedNodes(scene); + } +} + +void SceneUtils::RemoveMeshInstances(const std::vector &instances, + Scene *scene) { + // Remove mesh instances from the scene. + for (const SceneUtils::MeshInstance &instance : instances) { + const MeshGroupIndex mgi = + scene->GetNode(instance.scene_node_index)->GetMeshGroupIndex(); + + // Create a new mesh group with removed instance (we can't just delete the + // instance from the mesh group directly, because the same mesh group may + // be used by multiple scene nodes). + const MeshGroupIndex new_mesh_group_index = scene->AddMeshGroup(); + MeshGroup &new_mesh_group = *scene->GetMeshGroup(new_mesh_group_index); + + new_mesh_group.Copy(*scene->GetMeshGroup(mgi)); + new_mesh_group.RemoveMeshInstances(instance.mesh_index); + + // Assign the new mesh group to the scene node. Unused mesh groups will be + // automatically removed later during a scene cleanup operation. + scene->GetNode(instance.scene_node_index) + ->SetMeshGroupIndex(new_mesh_group_index); + } + + // Remove duplicate mesh groups that may have been created during the instance + // removal process. + DeduplicateMeshGroups(scene); +} + +void SceneUtils::DeduplicateMeshGroups(Scene *scene) { + if (scene->NumMeshGroups() <= 1) { + return; + } + + // Signature of a mesh group used for detecting duplicates. + struct MeshGroupSignature { + const MeshGroupIndex mesh_group_index; + const MeshGroup &mesh_group; + MeshGroupSignature(MeshGroupIndex mgi, const MeshGroup &mesh_group) + : mesh_group_index(mgi), mesh_group(mesh_group) {} + + bool operator==(const MeshGroupSignature &signature) const { + if (mesh_group.GetName() != signature.mesh_group.GetName()) { + return false; + } + if (mesh_group.NumMeshInstances() != + signature.mesh_group.NumMeshInstances()) { + return false; + } + // TODO(ostava): We may consider sorting meshes within a mesh group to + // make the order of meshes irrelevant. This should be done only for + // meshes with opaque materials though, because for transparent + // geometries, the order matters. + for (int i = 0; i < mesh_group.NumMeshInstances(); ++i) { + if (mesh_group.GetMeshInstance(i) != + signature.mesh_group.GetMeshInstance(i)) { + return false; + } + } + return true; + } + struct Hash { + size_t operator()(const MeshGroupSignature &signature) const { + size_t hash = 79; // Magic number. + const MeshGroup &group = signature.mesh_group; + hash = HashCombine(group.GetName(), hash); + hash = HashCombine(group.NumMeshInstances(), hash); + for (int i = 0; i < group.NumMeshInstances(); ++i) { + const MeshGroup::MeshInstance &instance = group.GetMeshInstance(i); + hash = HashCombine(instance.mesh_index, hash); + hash = HashCombine(instance.material_index, hash); + hash = HashCombine(instance.materials_variants_mappings.size(), hash); + for (const MeshGroup::MaterialsVariantsMapping &mapping : + instance.materials_variants_mappings) { + hash = HashCombine(mapping.material, hash); + hash = HashCombine(mapping.variants.size(), hash); + for (const int &variant : mapping.variants) { + hash = HashCombine(variant, hash); + } + } + } + return hash; + } + }; + }; + + // Set holding unique mesh groups. + std::unordered_set + unique_mesh_groups; + IndexTypeVector parent_mesh_group( + scene->NumMeshGroups()); + for (MeshGroupIndex mgi(0); mgi < scene->NumMeshGroups(); ++mgi) { + const MeshGroup *mg = scene->GetMeshGroup(mgi); + const MeshGroupSignature signature(mgi, *mg); + auto it = unique_mesh_groups.find(signature); + if (it != unique_mesh_groups.end()) { + parent_mesh_group[mgi] = it->mesh_group_index; + } else { + parent_mesh_group[mgi] = kInvalidMeshGroupIndex; + unique_mesh_groups.insert(signature); + } + } + + // Go over all nodes and update mesh groups if needed. + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const MeshGroupIndex mgi = scene->GetNode(sni)->GetMeshGroupIndex(); + if (mgi == kInvalidMeshGroupIndex || + parent_mesh_group[mgi] == kInvalidMeshGroupIndex) { + continue; // Nothing to update. + } + scene->GetNode(sni)->SetMeshGroupIndex(parent_mesh_group[mgi]); + } + + // Remove any unused mesh groups. + Cleanup(scene); +} + +void SceneUtils::SetDracoCompressionOptions( + const DracoCompressionOptions *options, Scene *scene) { + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + Mesh &mesh = scene->GetMesh(i); + if (options == nullptr) { + mesh.SetCompressionEnabled(false); + } else { + mesh.SetCompressionEnabled(true); + mesh.SetCompressionOptions(*options); + } + } +} + +bool SceneUtils::IsDracoCompressionEnabled(const Scene &scene) { + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + if (scene.GetMesh(i).IsCompressionEnabled()) { + return true; + } + } + return false; +} + +IndexTypeVector +SceneUtils::FindLargestBaseMeshTransforms(const Scene &scene) { + IndexTypeVector transforms( + scene.NumMeshes(), Eigen::Matrix4d::Identity()); + + // In case a mesh has multiple instances we want to use the instance with + // the largest scale. + IndexTypeVector transform_scale(scene.NumMeshes(), 0.f); + + const auto instances = SceneUtils::ComputeAllInstances(scene); + for (MeshInstanceIndex i(0); i < instances.size(); ++i) { + const auto &instance = instances[i]; + + // Compute the scale of the transform. + const Vector3f scale_vec(instance.transform.col(0).norm(), + instance.transform.col(1).norm(), + instance.transform.col(2).norm()); + + // In our framework we support uniform scale only. For now, just take the + // maximum scale across all axes. + // TODO(ostava): Investigate how to properly support non-uniform scaling. + const float max_scale = scale_vec.MaxCoeff(); + + if (transform_scale[instance.mesh_index] < max_scale) { + transform_scale[instance.mesh_index] = max_scale; + transforms[instance.mesh_index] = instance.transform; + } + } + + return transforms; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.h new file mode 100644 index 000000000..5b978c3c5 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils.h @@ -0,0 +1,150 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_SCENE_UTILS_H_ +#define DRACO_SCENE_SCENE_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/geometry_attribute.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Helper class containing various utility functions operating on draco::Scene. +class SceneUtils { + public: + // Helper struct holding instanced meshes and their transformations. + struct MeshInstance { + // Index of the parent mesh in the draco::Scene. + MeshIndex mesh_index; + // Index of the node in the draco::Scene. + SceneNodeIndex scene_node_index; + // Index of the mesh in the mesh group. + int mesh_group_mesh_index; + // Transform of the instance from the mesh local space to the global space + // of the scene. + Eigen::Matrix4d transform; + }; + + // Computes all mesh instances in the |scene|. + static IndexTypeVector ComputeAllInstances( + const Scene &scene); + + // Computes global transform matrix of a |scene| node given by its |index|. + static Eigen::Matrix4d ComputeGlobalNodeTransform(const Scene &scene, + SceneNodeIndex index); + + // Returns a vector of mesh instance counts for all base meshes. + static IndexTypeVector NumMeshInstances(const Scene &scene); + + // Returns the material index of the given |instance| or -1 if the mesh + // |instance| has a default material. + static int GetMeshInstanceMaterialIndex(const Scene &scene, + const MeshInstance &instance); + + // Returns the total number of faces on all base meshes of the scene (not + // counting instances). + static int NumFacesOnBaseMeshes(const Scene &scene); + + // Returns the total number of faces on all meshes of the scenes, including + // all instances of the same mesh. + static int NumFacesOnInstancedMeshes(const Scene &scene); + + // Returns the total number of points on all base meshes of the scene (not + // counting instances). + static int NumPointsOnBaseMeshes(const Scene &scene); + + // Returns the total number of points on all meshes of the scenes, including + // all instances of the same mesh. + static int NumPointsOnInstancedMeshes(const Scene &scene); + + // Returns the total number of attribute entries on all base meshes of the + // scene (not counting instances) for the first attribute of |att_type|. + static int NumAttEntriesOnBaseMeshes(const Scene &scene, + GeometryAttribute::Type att_type); + + // Returns the total number of attribute ent on all meshes of the scenes, + // including all instances of the same mesh for the first attribute of + // |att_type|. + static int NumAttEntriesOnInstancedMeshes(const Scene &scene, + GeometryAttribute::Type att_type); + + // Returns the bounding box of the scene. + static BoundingBox ComputeBoundingBox(const Scene &scene); + + // Returns the bounding box of a mesh instance. + static BoundingBox ComputeMeshInstanceBoundingBox( + const Scene &scene, const MeshInstance &instance); + + // Prints info about input and simplified scenes. + static void PrintInfo(const Scene &input, const Scene &simplified, + bool verbose); + + // Converts a draco::Mesh into a draco::Scene. If the passed-in `mesh` has + // multiple materials, the returned scene will contain multiple meshes, one + // for each of the source mesh's materials; if `mesh` has no material, one + // will be created for it. + static StatusOr> MeshToScene( + std::unique_ptr mesh); + + // Creates a mesh according to mesh |instance| in |scene|. Error is returned + // if there is no corresponding base mesh in the |scene| or the base mesh has + // no valid positions. + static StatusOr> InstantiateMesh( + const Scene &scene, const MeshInstance &instance); + + // Cleans up a |scene| by removing unused base meshes, unused and empty mesh + // groups, unused materials, unused texture coordinates and unused scene + // nodes. The actual behavior of the cleanup operation can be controller via + // the user provided |options|. + struct CleanupOptions { + bool remove_invalid_mesh_instances = true; + bool remove_unused_mesh_groups = true; + bool remove_unused_meshes = true; + bool remove_unused_nodes = false; + bool remove_unused_tex_coords = false; + bool remove_unused_materials = true; + }; + static void Cleanup(Scene *scene); + static void Cleanup(Scene *scene, const CleanupOptions &options); + + // Removes mesh |instances| from |scene|. + static void RemoveMeshInstances(const std::vector &instances, + Scene *scene); + + // Removes duplicate mesh groups that have the same name and that contain + // exactly the same meshes and materials. + static void DeduplicateMeshGroups(Scene *scene); + + // Enables geometry compression and sets compression |options| to all meshes + // in the |scene|. If |options| is nullptr then geometry compression is + // disabled for all meshes in the |scene|. + static void SetDracoCompressionOptions(const DracoCompressionOptions *options, + Scene *scene); + + // Returns true if geometry compression is eabled for any of |scene| meshes. + static bool IsDracoCompressionEnabled(const Scene &scene); + + // Returns a single tranformation matrix for each base mesh of the |scene| + // corresponding to the instance with the maximum scale. + static IndexTypeVector + FindLargestBaseMeshTransforms(const Scene &scene); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils_test.cc new file mode 100644 index 000000000..4d6bd731d --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/scene_utils_test.cc @@ -0,0 +1,763 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_utils.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/bounding_box.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" +#include "draco/scene/scene_indices.h" + +namespace { + +using draco::MeshIndex; +using draco::MeshInstanceIndex; + +void AssertMatrixNear(const Eigen::Matrix4d &a, const Eigen::Matrix4d &b, + float tolerance) { + Eigen::Matrix4d diff = a - b; + ASSERT_NEAR(diff.norm(), 0.f, tolerance) << a << " vs " << b; +} + +// TODO(fgalligan): Re-factor this code with gltf_encoder_test. +void CompareScenes(const draco::Scene *scene0, const draco::Scene *scene1) { + ASSERT_EQ(scene0->NumMeshGroups(), scene1->NumMeshGroups()); + ASSERT_EQ(scene0->NumMeshes(), scene1->NumMeshes()); + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterials(), + scene1->GetMaterialLibrary().NumMaterials()); + ASSERT_EQ(scene0->NumAnimations(), scene1->NumAnimations()); + ASSERT_EQ(scene0->NumSkins(), scene1->NumSkins()); + for (draco::AnimationIndex i(0); i < scene0->NumAnimations(); ++i) { + const draco::Animation *const animation0 = scene0->GetAnimation(i); + const draco::Animation *const animation1 = scene1->GetAnimation(i); + ASSERT_NE(animation0, nullptr); + ASSERT_NE(animation1, nullptr); + ASSERT_EQ(animation0->NumSamplers(), animation1->NumSamplers()); + ASSERT_EQ(animation0->NumChannels(), animation1->NumChannels()); + ASSERT_EQ(animation0->NumNodeAnimationData(), + animation1->NumNodeAnimationData()); + } +} + +TEST(SceneUtilsTest, TestComputeAllInstances) { + // Tests that we can compute all instances in an input scene along with their + // transformations. + + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Compute mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check base mesh indices. + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].mesh_index, 1); + ASSERT_EQ(instances[MeshInstanceIndex(2)].mesh_index, 2); + ASSERT_EQ(instances[MeshInstanceIndex(3)].mesh_index, 3); + ASSERT_EQ(instances[MeshInstanceIndex(4)].mesh_index, 3); + + // Check scene node indices. + ASSERT_EQ(instances[MeshInstanceIndex(0)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(2)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(3)].scene_node_index, 4); + ASSERT_EQ(instances[MeshInstanceIndex(4)].scene_node_index, 2); + + // Check indices of meshes in mesh group. + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_group_mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].mesh_group_mesh_index, 1); + ASSERT_EQ(instances[MeshInstanceIndex(2)].mesh_group_mesh_index, 2); + ASSERT_EQ(instances[MeshInstanceIndex(3)].mesh_group_mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(4)].mesh_group_mesh_index, 0); + + // The first three instances should have identity transformation. + for (MeshInstanceIndex i(0); i < 3; ++i) { + AssertMatrixNear(instances[i].transform, Eigen::Matrix4d::Identity(), + 1e-6f); + } + + // Fourth and fifth instances are transformed. + Eigen::Matrix4d expected_transform = Eigen::Matrix4d::Identity(); + // Expected translation. + expected_transform(0, 3) = -1.352329969406128; + expected_transform(1, 3) = 0.4277220070362091; + expected_transform(2, 3) = -2.98022992950564e-8; + + // Expected rotation. + Eigen::Matrix4d expected_rotation = Eigen::Matrix4d::Identity(); + expected_rotation.block<3, 3>(0, 0) = + Eigen::Quaterniond(-0.9960774183273317, -0.0, -0.0, 0.08848590403795243) + .normalized() + .toRotationMatrix(); + expected_transform = expected_transform * expected_rotation; + + AssertMatrixNear(instances[MeshInstanceIndex(3)].transform, + expected_transform, 1e-6f); + + // Last instance differs only in the translation part in X axis. + expected_transform(0, 3) = 1.432669997215271; + + AssertMatrixNear(instances[MeshInstanceIndex(4)].transform, + expected_transform, 1e-6f); +} + +TEST(SceneUtilsTest, TestComputeAllInstancesWithShiftedGeometryRoot) { + // Tests that we can compute all instances in an input scene along with their + // transformations. This scene has light and camera nodes before the geometry + // node. + auto scene = draco::ReadSceneFromTestFile( + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"); + ASSERT_NE(scene, nullptr); + + // There is one base mesh. + ASSERT_EQ(scene->NumMeshes(), 1); + + // There is a single mesh instance. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 1); + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_index, 0); + + // There is no transformation. + AssertMatrixNear(instances[MeshInstanceIndex(0)].transform, + Eigen::Matrix4d::Identity(), 1e-6); +} + +TEST(SceneUtilsTest, TestNumMeshInstances) { + // Tests that we can compute mesh instance counts for all base meshes in an + // input scene. + + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + const auto num_mesh_instances = draco::SceneUtils::NumMeshInstances(*scene); + ASSERT_EQ(num_mesh_instances.size(), 4); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(0)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(1)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(2)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(3)], 2); +} + +TEST(SceneUtilsTest, TestNumFacesOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumFacesOnBaseMeshes(*scene), 2856); + ASSERT_EQ(draco::SceneUtils::NumFacesOnInstancedMeshes(*scene), 3624); +} + +TEST(SceneUtilsTest, TestNumPointsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumPointsOnBaseMeshes(*scene), 2978); + ASSERT_EQ(draco::SceneUtils::NumPointsOnInstancedMeshes(*scene), 3564); +} + +TEST(SceneUtilsTest, TestNumPositionsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::POSITION), + 1572); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::POSITION), + 1960); +} + +TEST(SceneUtilsTest, TestNumNormalsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::NORMAL), + 1252); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::NORMAL), + 1612); +} + +TEST(SceneUtilsTest, TestNumColorsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::COLOR), + 0); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::COLOR), + 0); +} + +TEST(SceneUtilsTest, TestComputeBoundingBox) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + const draco::BoundingBox bbox = draco::SceneUtils::ComputeBoundingBox(*scene); + const draco::Vector3f min_point = bbox.GetMinPoint(); + const draco::Vector3f max_point = bbox.GetMaxPoint(); + constexpr float tolerance = 1e-4f; + EXPECT_NEAR(min_point[0], -2.43091, tolerance); + EXPECT_NEAR(min_point[1], +0.00145, tolerance); + EXPECT_NEAR(min_point[2], -1.39600, tolerance); + EXPECT_NEAR(max_point[0], +2.43800, tolerance); + EXPECT_NEAR(max_point[1], +2.58437, tolerance); + EXPECT_NEAR(max_point[2], +1.39600, tolerance); +} + +TEST(SceneUtilsTest, TestComputeMeshInstanceBoundingBox) { + auto scene = draco::ReadSceneFromTestFile( + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"); + ASSERT_NE(scene, nullptr); + const draco::BoundingBox scene_bbox = + draco::SceneUtils::ComputeBoundingBox(*scene); + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 1); + const draco::BoundingBox mesh_bbox = + draco::SceneUtils::ComputeMeshInstanceBoundingBox( + *scene, instances[draco::MeshInstanceIndex(0)]); + ASSERT_EQ(scene_bbox.GetMinPoint(), mesh_bbox.GetMinPoint()); + ASSERT_EQ(scene_bbox.GetMaxPoint(), mesh_bbox.GetMaxPoint()); +} + +TEST(SceneUtilsTest, TestMeshToSceneZeroMaterials) { + const std::string filename = "cube_att.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 0); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 1); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 1); +} + +TEST(SceneUtilsTest, TestMeshToSceneOneMaterial) { + const std::string filename = + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"; + auto scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 1); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 1); + + CompareScenes(scene.get(), scene_from_mesh.get()); +} + +TEST(SceneUtilsTest, TestMeshToSceneMultipleMaterials) { + const std::string filename = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 4); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 4); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 4); + + // Unfortunately we can't CompareScenes(scene.get(), scene_from_mesh.get()), + // because scene has two mesh groups and scene_from_mesh has only one. +} + +TEST(SceneUtilsTest, TestMeshToSceneMultipleMeshFeatures) { + const std::string filename = "BoxesMeta/glTF/BoxesMeta.gltf"; + std::unique_ptr scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 2); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 2); + + // Meshes of the new scene should have the same properties as meshes loaded + // directly into |scene|. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + ASSERT_EQ(scene->GetMesh(mi).NumMeshFeatures(), + scene_from_mesh->GetMesh(mi).NumMeshFeatures()); + for (draco::MeshFeaturesIndex mfi(0); + mfi < scene->GetMesh(mi).NumMeshFeatures(); ++mfi) { + const auto &scene_mf = scene->GetMesh(mi).GetMeshFeatures(mfi); + const auto &scene_from_mesh_mf = + scene_from_mesh->GetMesh(mi).GetMeshFeatures(mfi); + ASSERT_EQ(scene_mf.GetAttributeIndex(), + scene_from_mesh_mf.GetAttributeIndex()); + ASSERT_EQ(scene_mf.GetPropertyTableIndex(), + scene_from_mesh_mf.GetPropertyTableIndex()); + ASSERT_EQ(scene_mf.GetLabel(), scene_from_mesh_mf.GetLabel()); + ASSERT_EQ(scene_mf.GetNullFeatureId(), + scene_from_mesh_mf.GetNullFeatureId()); + ASSERT_EQ(scene_mf.GetFeatureCount(), + scene_from_mesh_mf.GetFeatureCount()); + ASSERT_EQ(scene_mf.GetTextureChannels(), + scene_from_mesh_mf.GetTextureChannels()); + ASSERT_EQ(scene_mf.GetTextureMap().texture() != nullptr, + scene_from_mesh_mf.GetTextureMap().texture() != nullptr); + } + } +} + +TEST(SceneUtilsTest, TestInstantiateMeshWithIdentityTransformation) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Compute scene mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check instantiation of mesh with identity transformation. + const draco::SceneUtils::MeshInstance instance = + instances[MeshInstanceIndex(0)]; + ASSERT_EQ(instance.transform, Eigen::Matrix4d::Identity()); + + // Instantiate this mesh instance. + DRACO_ASSIGN_OR_ASSERT(auto mesh, + draco::SceneUtils::InstantiateMesh(*scene, instance)); + const draco::Mesh &base_mesh = scene->GetMesh(instance.mesh_index); + + // Check that bounding box of the instanced mesh is same as box of base mesh. + const draco::BoundingBox instanced_bbox = mesh->ComputeBoundingBox(); + const draco::BoundingBox base_bbox = base_mesh.ComputeBoundingBox(); + ASSERT_EQ(instanced_bbox.GetMinPoint()[0], base_bbox.GetMinPoint()[0]); + ASSERT_EQ(instanced_bbox.GetMinPoint()[1], base_bbox.GetMinPoint()[1]); + ASSERT_EQ(instanced_bbox.GetMinPoint()[2], base_bbox.GetMinPoint()[2]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[0], base_bbox.GetMaxPoint()[0]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[1], base_bbox.GetMaxPoint()[1]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[2], base_bbox.GetMaxPoint()[2]); +} + +TEST(SceneUtilsTest, TestInstantiateMesh) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Compute scene mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check instantiation of mesh with identity transformation. + const draco::SceneUtils::MeshInstance instance = + instances[MeshInstanceIndex(3)]; + ASSERT_NE(instance.transform, Eigen::Matrix4d::Identity()); + + // Instantiate this mesh instance. + DRACO_ASSIGN_OR_ASSERT(auto mesh, + draco::SceneUtils::InstantiateMesh(*scene, instance)); + const draco::Mesh &base_mesh = scene->GetMesh(instance.mesh_index); + + // Check bounding box of the base mesh. + constexpr float tolerance = 1e-4f; + const draco::BoundingBox base_bbox = base_mesh.ComputeBoundingBox(); + EXPECT_NEAR(base_bbox.GetMinPoint()[0], -0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMinPoint()[1], -0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMinPoint()[2], -1.05800, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[0], +0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[1], +0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[2], +1.05800, tolerance); + + // Check bounding box of the instanced mesh. It should differ. + const draco::BoundingBox instanced_bbox = mesh->ComputeBoundingBox(); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[0], -1.77860, tolerance); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[1], +0.00145, tolerance); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[2], -1.05800, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[0], -0.92606, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[1], +0.85399, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[2], +1.05800, tolerance); +} + +TEST(SceneUtilsTest, TestCleanupEmptyMeshGroup) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Invalidate references to the three truck body parts in mesh group. + draco::MeshGroup &mesh_group = *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + mesh_group.SetMeshInstance(0, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(1, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(2, {draco::kInvalidMeshIndex, 0}); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 2); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); +} + +TEST(SceneUtilsTest, TestCleanupUnreferencedMeshGroup) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + + // Invalidate references to truck axle mesh group. + scene->GetNode(draco::SceneNodeIndex(2)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + scene->GetNode(draco::SceneNodeIndex(4)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 3); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 3); +} + +TEST(SceneUtilsTest, TestCleanupInvalidMeshIndex) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Invalidate references to two truck body parts in mesh group. + draco::MeshGroup &mesh_group = *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + mesh_group.SetMeshInstance(0, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(2, {draco::kInvalidMeshIndex, 0}); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 2); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 3); + ASSERT_EQ(scene->GetMeshGroup(draco::MeshGroupIndex(0))->NumMeshInstances(), + 1); +} + +TEST(SceneUtilsTest, TestCleanupUnusedNodes) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumNodes(), 5); + + draco::SceneUtils::CleanupOptions options; + options.remove_unused_nodes = true; + + // Delete mesh on node 2 and try to remove unused nodes. + // Node 2 is connected to node 1 that has no mesh as well. But node 2 is also + // used in an animation so we don't actually expect anything to be deleted. + scene->GetNode(draco::SceneNodeIndex(2)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + draco::SceneUtils::Cleanup(scene.get(), options); + + ASSERT_EQ(scene->NumNodes(), 5); + + // Now remove the animation channel that used the node and try it again. This + // time, we expect two nodes to be deleted (node 1 and node 2). Node 1 will be + // deleted because it doesn't contain a mesh and all its children are unused. + ASSERT_EQ(scene->GetAnimation(draco::AnimationIndex(0)) + ->GetChannel(0) + ->target_index, + 2); + // Change the mapped node to node 4 (we can't actually remove channel as of + // the time this test was written). + scene->GetAnimation(draco::AnimationIndex(0))->GetChannel(0)->target_index = + 4; + + // Cleanup again. + draco::SceneUtils::Cleanup(scene.get(), options); + ASSERT_EQ(scene->NumNodes(), 3); // Two nodes should be deleted. + + // Ensure all node indices are remapped to the new values. + for (draco::SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const auto *node = scene->GetNode(sni); + for (int i = 0; i < node->NumChildren(); ++i) { + ASSERT_LT(node->Child(i).value(), 3); + } + for (int i = 0; i < node->NumParents(); ++i) { + ASSERT_LT(node->Parent(i).value(), 3); + } + } + + // Ensure the animation channels are mapped to the updated node indices (node + // 4 should be new node 2 because two nodes were removed). + ASSERT_EQ(scene->GetAnimation(draco::AnimationIndex(0)) + ->GetChannel(0) + ->target_index, + 2); +} + +TEST(SceneUtilsTest, TestDeduplicateMeshGroups) { + // Input scene has four different mesh groups but only two of them should + // contain unique set of meshes. + auto scene = + draco::ReadSceneFromTestFile("DuplicateMeshes/duplicate_meshes.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 4); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 7); + + draco::SceneUtils::DeduplicateMeshGroups(scene.get()); + + // Check deduplicated scene. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 7); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoordsNoTextures) { + // The glTF file has two tex coords that are unused because the materials do + // not reference any textures. + auto scene = draco::ReadSceneFromTestFile("UnusedTexCoords/NoTextures.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); + + // Cleanup scene and check that unused UV are not removed by default. + draco::SceneUtils::Cleanup(scene.get()); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); + + // Cleanup scene and check that unused UV are removed when requested. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 0); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoords0NoReferences) { + auto scene = draco::ReadSceneFromTestFile( + "UnusedTexCoords/TexCoord0InvalidTexCoord1Valid.gltf"); + ASSERT_NE(scene, nullptr); + typedef draco::GeometryAttribute Att; + + draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(0)); + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 2); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 1)->size(), 4); + auto &ml = scene->GetMaterialLibrary(); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 1); + + // Cleanup unused texture coordinate attributes. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + + // Check that the unreferenced attribute was removed. + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 1); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 4); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoords1NoReferences) { + auto scene = draco::ReadSceneFromTestFile( + "UnusedTexCoords/TexCoord0ValidTexCoord1Invalid.gltf"); + ASSERT_NE(scene, nullptr); + typedef draco::GeometryAttribute Att; + + draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(0)); + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 2); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 1)->size(), 4); + auto &ml = scene->GetMaterialLibrary(); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); + + // Cleanup unused texture coordinate attributes. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + + // Check that the unreferenced attribute was removed. + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 1); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); +} + +TEST(SceneUtilsTest, TestComputeGlobalNodeTransform) { + // Tests that we can compute global transformation of scene nodes. + + auto scene = draco::ReadSceneFromTestFile("simple_skin.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumNodes(), 3); + + // Compute and check global node transforms. + constexpr float kTolerance = 1e-6; + // clang-format off + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(0)), + Eigen::Matrix4d::Identity(), + kTolerance); + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(1)), + Eigen::Matrix4d{{1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0}}, + kTolerance); + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(2)), + Eigen::Matrix4d{{1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0}}, + kTolerance); + // clang-format on +} + +TEST(SceneUtilsTest, TestIsDracoCompressionEnabled) { + // Tests that we can determine whether any of the scene meshes have geometry + // compression enabled. + const std::string file = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(file); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Check that the scene has geometry compression disabled by default. + ASSERT_FALSE(draco::SceneUtils::IsDracoCompressionEnabled(*scene)); + + // Check that geometry compression can be enabled. + scene->GetMesh(MeshIndex(2)).SetCompressionEnabled(true); + ASSERT_TRUE(draco::SceneUtils::IsDracoCompressionEnabled(*scene)); +} + +TEST(SceneUtilsTest, TestSetDracoCompressionOptions) { + // Tests that geometry compression settings can be set for all scene meshes. + const std::string file = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(file); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Check that compression is initially disabled for all scene meshes. + ASSERT_FALSE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); + + // Check that initially all scene meshes have default compression options. + draco::DracoCompressionOptions defaults; + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(1)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(2)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(3)).GetCompressionOptions(), defaults); + + // Check geometry compression options can be set to all scene meshes and that + // this also enables compression for all scnene meshes. + draco::DracoCompressionOptions options; + options.compression_level = 10; + options.quantization_bits_normal = 12; + draco::SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(1)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(2)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(3)).GetCompressionOptions(), options); + + // Check that geometry compression can be disabled for all scene meshes. + draco::SceneUtils::SetDracoCompressionOptions(nullptr, scene.get()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); +} + +TEST(SceneUtilsTest, TestFindLargestBaseMeshTransforms) { + // Tests that FindLargestBaseMeshTransforms() works as expected. + auto scene = + draco::ReadSceneFromTestFile("CubeScaledInstances/glTF/cube_att.gltf"); + ASSERT_NE(scene, nullptr); + + // There should be one base mesh with four instances. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 4); + + const auto transforms = + draco::SceneUtils::FindLargestBaseMeshTransforms(*scene); + + ASSERT_EQ(transforms.size(), 1); // One transform for the single base mesh. + + // The largest instance should have a uniform scale 4. + const draco::MeshIndex mi(0); + ASSERT_EQ(transforms[mi].diagonal(), Eigen::Vector4d(4, 4, 4, 1)); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.cc new file mode 100644 index 000000000..6e6dac251 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.cc @@ -0,0 +1,102 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/trs_matrix.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void TrsMatrix::Copy(const TrsMatrix &tm) { + matrix_ = tm.matrix_; + translation_ = tm.translation_; + rotation_ = tm.rotation_; + scale_ = tm.scale_; + matrix_set_ = tm.matrix_set_; + translation_set_ = tm.translation_set_; + rotation_set_ = tm.rotation_set_; + scale_set_ = tm.scale_set_; +} + +Eigen::Matrix4d TrsMatrix::ComputeTransformationMatrix() const { + // Return transformation matrix if it has been set. + if (matrix_set_) { + return matrix_; + } + + // Populate translation matrix. + Eigen::Matrix4d translation_matrix = Eigen::Matrix4d::Identity(); + translation_matrix(0, 3) = translation_[0]; + translation_matrix(1, 3) = translation_[1]; + translation_matrix(2, 3) = translation_[2]; + + // Populate rotation matrix using rotation quaternion. + Eigen::Matrix3d rotation_matrix_3 = rotation_.normalized().toRotationMatrix(); + + // Convert the 3x3 matrix to a 4x4 matrix that can be multiplied with the + // other TRS matrices. + Eigen::Matrix4d rotation_matrix = Eigen::Matrix4d::Identity(); + rotation_matrix.block<3, 3>(0, 0) = rotation_matrix_3; + + // Populate scale matrix. + const Eigen::Matrix4d scale_matrix( + Eigen::Vector4d(scale_.x(), scale_.y(), scale_.z(), 1.0).asDiagonal()); + + // Return transformation matrix computed by combining TRS matrices. + return translation_matrix * rotation_matrix * scale_matrix; +} + +bool TrsMatrix::IsMatrixIdentity() const { + if (!matrix_set_) { + return true; + } + return matrix_ == Eigen::Matrix4d::Identity(); +} + +bool TrsMatrix::IsMatrixTranslationOnly() const { + if (!matrix_set_) { + return false; + } + Eigen::Matrix4d translation_check = matrix_; + translation_check(0, 3) = 0.0; + translation_check(1, 3) = 0.0; + translation_check(2, 3) = 0.0; + return translation_check == Eigen::Matrix4d::Identity(); +} + +bool TrsMatrix::operator==(const TrsMatrix &trs_matrix) const { + if (matrix_set_ != trs_matrix.matrix_set_ || + translation_set_ != trs_matrix.translation_set_ || + rotation_set_ != trs_matrix.rotation_set_ || + scale_set_ != trs_matrix.scale_set_) { + return false; + } + if (matrix_set_ && matrix_ != trs_matrix.matrix_) { + return false; + } + if (translation_set_ && translation_ != trs_matrix.translation_) { + return false; + } + if (rotation_set_ && rotation_ != trs_matrix.rotation_) { + return false; + } + if (scale_set_ && scale_set_ != trs_matrix.scale_set_) { + return false; + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.h b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.h new file mode 100644 index 000000000..6c2ab7388 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix.h @@ -0,0 +1,124 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_TRS_MATRIX_H_ +#define DRACO_SCENE_TRS_MATRIX_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/core/status_or.h" + +namespace draco { + +// This class is used to store one or more of a translation, rotation, scale +// vectors or a transformation matrix. +class TrsMatrix { + public: + TrsMatrix() + : matrix_(Eigen::Matrix4d::Identity()), + translation_(0.0, 0.0, 0.0), + rotation_(1.0, 0.0, 0.0, 0.0), // (w, x, y, z) + scale_(1.0, 1.0, 1.0), + matrix_set_(false), + translation_set_(false), + rotation_set_(false), + scale_set_(false) {} + + void Copy(const TrsMatrix &tm); + + void SetMatrix(const Eigen::Matrix4d &matrix) { + matrix_ = matrix; + matrix_set_ = true; + } + bool MatrixSet() const { return matrix_set_; } + StatusOr Matrix() const { + if (!matrix_set_) { + return Status(Status::DRACO_ERROR, "Matrix is not set."); + } + return matrix_; + } + + void SetTranslation(const Eigen::Vector3d &translation) { + translation_ = translation; + translation_set_ = true; + } + bool TranslationSet() const { return translation_set_; } + StatusOr Translation() const { + if (!translation_set_) { + return Status(Status::DRACO_ERROR, "Translation is not set."); + } + return translation_; + } + + void SetRotation(const Eigen::Quaterniond &rotation) { + rotation_ = rotation; + rotation_set_ = true; + } + bool RotationSet() const { return rotation_set_; } + StatusOr Rotation() const { + if (!rotation_set_) { + return Status(Status::DRACO_ERROR, "Rotation is not set."); + } + return rotation_; + } + + void SetScale(const Eigen::Vector3d &scale) { + scale_ = scale; + scale_set_ = true; + } + bool ScaleSet() const { return scale_set_; } + StatusOr Scale() const { + if (!scale_set_) { + return Status(Status::DRACO_ERROR, "Scale is not set."); + } + return scale_; + } + + // Returns true if the matrix is not set or if matrix is set and is equal to + // identity. + bool IsMatrixIdentity() const; + + // Returns true if matrix is set and only the translation elements may differ + // from identity. Returns false if matrix is not set. + bool IsMatrixTranslationOnly() const; + + // Returns transformation matrix if it has been set. Otherwise, computes + // transformation matrix from TRS vectors and returns it. + Eigen::Matrix4d ComputeTransformationMatrix() const; + + // Returns a boolean indicating whether any of the transforms have been set. + // Can be used to check whether this object represents a default transform. + bool TransformSet() const { + return matrix_set_ || translation_set_ || rotation_set_ || scale_set_; + } + + bool operator==(const TrsMatrix &trs_matrix) const; + + private: + Eigen::Matrix4d matrix_; + Eigen::Vector3d translation_; + Eigen::Quaterniond rotation_; + Eigen::Vector3d scale_; + bool matrix_set_; + bool translation_set_; + bool rotation_set_; + bool scale_set_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_TRS_MATRIX_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix_test.cc new file mode 100644 index 000000000..d7938e974 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/scene/trs_matrix_test.cc @@ -0,0 +1,79 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/trs_matrix.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(TrsMatrixTest, TestIsMatrixIdentity) { + draco::TrsMatrix trs; + ASSERT_EQ(trs.MatrixSet(), false); + ASSERT_EQ(trs.IsMatrixIdentity(), true); + + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16; + // clang-format on + trs.SetMatrix(matrix); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixIdentity(), false); + + trs.SetMatrix(Eigen::Matrix4d::Identity()); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixIdentity(), true); +} + +TEST(TrsMatrixTest, TestIsMatrixTranslationOnly) { + draco::TrsMatrix trs; + ASSERT_EQ(trs.MatrixSet(), false); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), false); + + trs.SetMatrix(Eigen::Matrix4d::Identity()); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), true); + + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16; + // clang-format on + trs.SetMatrix(matrix); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), false); + + // clang-format off + Eigen::Matrix4d translation; + translation << 1, 0, 0, 1, + 0, 1, 0, 2, + 0, 0, 1, 3, + 0, 0, 0, 1; + // clang-format on + trs.SetMatrix(translation); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), true); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.cc new file mode 100644 index 000000000..b4d493250 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.cc @@ -0,0 +1,29 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/source_image.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void SourceImage::Copy(const SourceImage &src) { + mime_type_ = src.mime_type_; + filename_ = src.filename_; + encoded_data_ = src.encoded_data_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.h new file mode 100644 index 000000000..5827918e4 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/source_image.h @@ -0,0 +1,72 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_SOURCE_IMAGE_H_ +#define DRACO_TEXTURE_SOURCE_IMAGE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/core/status.h" + +namespace draco { + +// This class is used to hold the encoded and decoded data and characteristics +// for an image. In order for the image to contain "valid" encoded data, either +// the |filename_| must point to a valid image file or the |mime_type_| and +// |encoded_data_| must contain valid image data. +class SourceImage { + public: + SourceImage() {} + + // No copy constructors. + SourceImage(const SourceImage &) = delete; + SourceImage &operator=(const SourceImage &) = delete; + // No move constructors. + SourceImage(SourceImage &&) = delete; + SourceImage &operator=(SourceImage &&) = delete; + + void Copy(const SourceImage &src); + + // Sets the name of the source image file. + void set_filename(const std::string &filename) { filename_ = filename; } + const std::string &filename() const { return filename_; } + + void set_mime_type(const std::string &mime_type) { mime_type_ = mime_type; } + const std::string &mime_type() const { return mime_type_; } + + std::vector &MutableEncodedData() { return encoded_data_; } + const std::vector &encoded_data() const { return encoded_data_; } + + private: + // The filename of the image. This field can be empty as long as |mime_type_| + // and |encoded_data_| is not empty. + std::string filename_; + + // The mimetype of the |encoded_data_|. + std::string mime_type_; + + // The encoded data of the image. This field can be empty as long as + // |filename_| is not empty. + std::vector encoded_data_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_SOURCE_IMAGE_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture.h new file mode 100644 index 000000000..1d3b6e382 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture.h @@ -0,0 +1,46 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_H_ +#define DRACO_TEXTURE_TEXTURE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/io/image_compression_options.h" +#include "draco/texture/source_image.h" + +namespace draco { + +// Texture class storing the source image data. +class Texture { + public: + void Copy(Texture &other) { source_image_.Copy(other.source_image_); } + + void set_source_image(const SourceImage &image) { source_image_.Copy(image); } + const SourceImage &source_image() const { return source_image_; } + SourceImage &source_image() { return source_image_; } + + private: + // If set this is the image that this texture is based from. + SourceImage source_image_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.cc new file mode 100644 index 000000000..221ff28d4 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.cc @@ -0,0 +1,61 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_library.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void TextureLibrary::Copy(const TextureLibrary &src) { + Clear(); + Append(src); +} + +void TextureLibrary::Append(const TextureLibrary &src) { + const size_t old_num_textures = textures_.size(); + textures_.resize(old_num_textures + src.textures_.size()); + for (int i = 0; i < src.textures_.size(); ++i) { + textures_[old_num_textures + i] = std::unique_ptr(new Texture()); + textures_[old_num_textures + i]->Copy(*src.textures_[i]); + } +} + +void TextureLibrary::Clear() { textures_.clear(); } + +int TextureLibrary::PushTexture(std::unique_ptr texture) { + textures_.push_back(std::move(texture)); + return textures_.size() - 1; +} + +std::unordered_map +TextureLibrary::ComputeTextureToIndexMap() const { + std::unordered_map ret; + for (int i = 0; i < textures_.size(); ++i) { + ret[textures_[i].get()] = i; + } + return ret; +} + +std::unique_ptr TextureLibrary::RemoveTexture(int index) { + std::unique_ptr ret = std::move(textures_[index]); + textures_.erase(textures_.begin() + index); + return ret; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.h new file mode 100644 index 000000000..a377d8fbc --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library.h @@ -0,0 +1,67 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_LIBRARY_H_ +#define DRACO_TEXTURE_TEXTURE_LIBRARY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/texture/texture.h" + +namespace draco { + +// Container class for storing draco::Texture objects in an indexed list. +class TextureLibrary { + public: + // Copies textures from the source library to this library. Order of the + // copied textures is preserved. + void Copy(const TextureLibrary &src); + + // Appends all textures from the source library to this library. All textures + // are copied over. + void Append(const TextureLibrary &src); + + // Removes all textures from the library. + void Clear(); + + // Pushes a new texture into the library. Returns an index of the newly + // inserted texture. + int PushTexture(std::unique_ptr texture); + + size_t NumTextures() const { return textures_.size(); } + + Texture *GetTexture(int index) { return textures_[index].get(); } + const Texture *GetTexture(int index) const { return textures_[index].get(); } + + // Returns a map from texture pointer to texture index for all textures. + std::unordered_map ComputeTextureToIndexMap() const; + + // Removes and returns a texture from the library. The returned texture can be + // either used by the caller or ignored in which case it would be + // automatically deleted. + std::unique_ptr RemoveTexture(int index); + + private: + std::vector> textures_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_LIBRARY_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library_test.cc new file mode 100644 index 000000000..4d681fdd2 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_library_test.cc @@ -0,0 +1,22 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_library.h" + +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace {} // namespace diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.cc new file mode 100644 index 000000000..459d3f600 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.cc @@ -0,0 +1,86 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_map.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +TextureMap::TextureMap() + : type_(TextureMap::GENERIC), + wrapping_mode_(CLAMP_TO_EDGE), + tex_coord_index_(-1), + min_filter_(UNSPECIFIED), + mag_filter_(UNSPECIFIED), + texture_(nullptr) {} + +void TextureMap::Copy(const TextureMap &src) { + type_ = src.type_; + wrapping_mode_ = src.wrapping_mode_; + tex_coord_index_ = src.tex_coord_index_; + min_filter_ = src.min_filter_; + mag_filter_ = src.mag_filter_; + if (src.owned_texture_ == nullptr) { + owned_texture_ = nullptr; + texture_ = src.texture_; + } else { + std::unique_ptr new_texture(new Texture()); + new_texture->Copy(*src.owned_texture_); + owned_texture_ = std::move(new_texture); + texture_ = owned_texture_.get(); + } + texture_transform_.Copy(src.texture_transform_); +} + +void TextureMap::SetProperties(Type type) { + SetProperties(type, WrappingMode(CLAMP_TO_EDGE), 0); +} + +void TextureMap::SetProperties(TextureMap::Type type, int tex_coord_index) { + SetProperties(type, WrappingMode(CLAMP_TO_EDGE), tex_coord_index); +} + +void TextureMap::SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index) { + SetProperties(type, wrapping_mode, tex_coord_index, UNSPECIFIED, UNSPECIFIED); +} + +void TextureMap::SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index, FilterType min_filter, + FilterType mag_filter) { + type_ = type; + wrapping_mode_ = wrapping_mode; + tex_coord_index_ = tex_coord_index; + min_filter_ = min_filter; + mag_filter_ = mag_filter; +} + +void TextureMap::SetTexture(std::unique_ptr texture) { + owned_texture_ = std::move(texture); + texture_ = owned_texture_.get(); +} + +void TextureMap::SetTexture(Texture *texture) { + owned_texture_ = nullptr; + texture_ = texture; +} + +void TextureMap::SetTransform(const TextureTransform &transform) { + texture_transform_.Copy(transform); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.h new file mode 100644 index 000000000..f3a95b501 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map.h @@ -0,0 +1,175 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_MAP_H_ +#define DRACO_TEXTURE_TEXTURE_MAP_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/texture/texture.h" +#include "draco/texture/texture_transform.h" + +namespace draco { + +// Class representing mapping of one texture to a mesh. A texture map +// specifies the mesh attribute that contains texture coordinates used by the +// texture. The class also defines an intended use of the texture as a so called +// mapping type (COLOR, NORMAL_TANGENT_SPACE, etc..). Mapping types are roughly +// based on GLTF 2.0 material spec that describes a metallic-roughness PBR +// material model. More details can be found here: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials +class TextureMap { + public: + enum Type { + // Generic purpose texture (not GLTF compliant). + GENERIC = 0, + // Color data with optional alpha channel for transparency (GLTF compliant). + COLOR = 1, + // Dedicated texture for storing transparency (not GLTF compliant). + OPACITY = 2, + // Dedicated texture for storing metallic property (not GLTF compliant). + METALLIC = 3, + // Dedicated texture for storing roughness property (not GLTF compliant). + ROUGHNESS = 4, + // Combined texture for storing metallic and roughness properties. + // B == metallic, G == roughness (GLTF compliant). + METALLIC_ROUGHNESS = 5, + // Normal map defined in the object space of the mesh (not GLTF compliant). + NORMAL_OBJECT_SPACE = 6, + // Normal map defined in the tangent space of the mesh (GLTF compliant). + NORMAL_TANGENT_SPACE = 7, + // Precomputed ambient occlusion on the surface (GLTF compliant). + AMBIENT_OCCLUSION = 8, + // Emissive color (GLTF compliant). + EMISSIVE = 9, + // Texture types of glTF material extension KHR_materials_sheen. + SHEEN_COLOR = 10, + SHEEN_ROUGHNESS = 11, + // Texture types of glTF material extension KHR_materials_transmission. + TRANSMISSION = 12, + // Texture types of glTF material extension KHR_materials_clearcoat. + CLEARCOAT = 13, + CLEARCOAT_ROUGHNESS = 14, + CLEARCOAT_NORMAL = 15, + // Texture types of glTF material extension KHR_materials_volume. + THICKNESS = 16, + // Texture types of glTF material extension KHR_materials_specular. + SPECULAR = 17, + SPECULAR_COLOR = 18, + // The number of texture types. + TEXTURE_TYPES_COUNT + }; + + enum AxisWrappingMode { + // Out of bounds access along a texture axis should be clamped to the + // nearest edge (default). + CLAMP_TO_EDGE = 0, + // Texture is repeated along a texture axis in a mirrored pattern. + MIRRORED_REPEAT, + // Texture is repeated along a texture axis (tiled textures). + REPEAT + }; + + struct WrappingMode { + explicit WrappingMode(AxisWrappingMode mode) : WrappingMode(mode, mode) {} + WrappingMode(AxisWrappingMode s, AxisWrappingMode t) : s(s), t(t) {} + AxisWrappingMode s; + AxisWrappingMode t; + }; + + // Filter types are roughly based on glTF 2.0 samplers spec. + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#samplers + enum FilterType { + UNSPECIFIED = 0, + NEAREST, + LINEAR, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR + }; + + TextureMap(); + TextureMap(TextureMap &&) = default; + + // Copies texture map data from the |src| texture map to this texture map. + void Copy(const TextureMap &src); + + // Sets the mapping information between the texture and the target mesh. + // |tex_coord_index| is the local index of the texture coordinates that is + // used to map the texture on the mesh. + void SetProperties(Type type); + void SetProperties(Type type, int tex_coord_index); + void SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index); + void SetProperties(Type type, WrappingMode wrapping_mode, int tex_coord_index, + FilterType min_filter, FilterType mag_filter); + + // Set texture and transfer its ownership to the TextureMap object. + // + // Note that this should not be used if this TextureMap is part of a + // MaterialLibrary. For such cases, the TextureMap's texture should refer to + // an entry in the MaterialLibrary's TextureLibrary. + void SetTexture(std::unique_ptr texture); + + // Set texture and without transferring the ownership. The caller needs to + // ensure the texture is valid during the lifetime of the TextureMap object. + void SetTexture(Texture *texture); + + void SetTransform(const TextureTransform &transform); + const TextureTransform &texture_transform() const { + return texture_transform_; + } + + const Texture *texture() const { return texture_; } + Texture *texture() { return texture_; } + Type type() const { return type_; } + WrappingMode wrapping_mode() const { return wrapping_mode_; } + int tex_coord_index() const { return tex_coord_index_; } + FilterType min_filter() const { return min_filter_; } + FilterType mag_filter() const { return mag_filter_; } + + TextureMap &operator=(TextureMap &&) = default; + + private: + Type type_; + WrappingMode wrapping_mode_; + + // Local index of the texture coordinates that is used to map the texture on + // the mesh. For example, |tex_coord_index_ == 0| would correspond to the + // first TEX_COORD attribute of the mesh. + int tex_coord_index_; + + FilterType min_filter_; + FilterType mag_filter_; + + // Used when the texture object is owned by TextureMap, otherwise set to + // nullptr. + std::unique_ptr owned_texture_; + + // Either raw pointer owned by |owned_texture_| or a pointer to a user + // specified texture in case |owned_texture_| is nullptr. + Texture *texture_; + + // Transformation values of the texture map. + TextureTransform texture_transform_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_MAP_H_ diff --git a/Engine/lib/assimp/contrib/gtest/build-aux/.keep b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map_test.cc similarity index 100% rename from Engine/lib/assimp/contrib/gtest/build-aux/.keep rename to Engine/lib/assimp/contrib/draco/src/draco/texture/texture_map_test.cc diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.cc new file mode 100644 index 000000000..ccb00d59f --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.cc @@ -0,0 +1,79 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_transform.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +TextureTransform::TextureTransform() { Clear(); } + +void TextureTransform::Clear() { + offset_ = TextureTransform::GetDefaultOffset(); + rotation_ = TextureTransform::GetDefaultRotation(); + scale_ = TextureTransform::GetDefaultScale(); + tex_coord_ = TextureTransform::GetDefaultTexCoord(); +} + +void TextureTransform::Copy(const TextureTransform &src) { + offset_ = src.offset_; + rotation_ = src.rotation_; + scale_ = src.scale_; + tex_coord_ = src.tex_coord_; +} + +bool TextureTransform::IsDefault(const TextureTransform &tt) { + const TextureTransform defaults; + if (tt == defaults) { + return true; + } + return false; +} + +bool TextureTransform::IsOffsetSet() const { + return offset_ != TextureTransform::GetDefaultOffset(); +} + +bool TextureTransform::IsRotationSet() const { + return rotation_ != TextureTransform::GetDefaultRotation(); +} + +bool TextureTransform::IsScaleSet() const { + return scale_ != TextureTransform::GetDefaultScale(); +} + +bool TextureTransform::IsTexCoordSet() const { + return tex_coord_ != TextureTransform::GetDefaultTexCoord(); +} + +bool TextureTransform::operator==(const TextureTransform &tt) const { + if (tex_coord_ != tt.tex_coord_) { + return false; + } + if (rotation_ != tt.rotation_) { + return false; + } + if (offset_ != tt.offset_) { + return false; + } + if (scale_ != tt.scale_) { + return false; + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.h new file mode 100644 index 000000000..b2ec47f2e --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform.h @@ -0,0 +1,75 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ +#define DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +namespace draco { + +// Class to hold texture transformations. Parameters are based on the glTF 2.0 +// extension KHR_texture_transform: +// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform +class TextureTransform { + public: + TextureTransform(); + + // Resets the values back to defaults. + void Clear(); + + // Copies texture transform data from the |src| texture transform to this + // texture transform. + void Copy(const TextureTransform &src); + + // Returns true if |tt| contains all default values. + static bool IsDefault(const TextureTransform &tt); + + bool IsOffsetSet() const; + bool IsRotationSet() const; + bool IsScaleSet() const; + bool IsTexCoordSet() const; + + void set_offset(const std::array &offset) { offset_ = offset; } + const std::array &offset() const { return offset_; } + void set_scale(const std::array &scale) { scale_ = scale; } + const std::array &scale() const { return scale_; } + + void set_rotation(double rotation) { rotation_ = rotation; } + double rotation() const { return rotation_; } + void set_tex_coord(int tex_coord) { tex_coord_ = tex_coord; } + int tex_coord() const { return tex_coord_; } + + bool operator==(const TextureTransform &tt) const; + + private: + static std::array GetDefaultOffset() { return {0.0, 0.0}; } + static float GetDefaultRotation() { return 0.0; } + static std::array GetDefaultScale() { return {0.0, 0.0}; } + static int GetDefaultTexCoord() { return -1; } + + std::array offset_; + double rotation_; + std::array scale_; + int tex_coord_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ diff --git a/Engine/lib/assimp/test/models/glTF2/textureTransform/License.txt b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform_test.cc similarity index 100% rename from Engine/lib/assimp/test/models/glTF2/textureTransform/License.txt rename to Engine/lib/assimp/contrib/draco/src/draco/texture/texture_transform_test.cc diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.cc new file mode 100644 index 000000000..7598ac780 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.cc @@ -0,0 +1,144 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include +#include + +namespace draco { + +std::string TextureUtils::GetTargetStem(const Texture &texture) { + // Return stem of the source image if there is one. + if (!texture.source_image().filename().empty()) { + const std::string &full_path = texture.source_image().filename(); + std::string folder_path; + std::string filename; + SplitPath(full_path, &folder_path, &filename); + return RemoveFileExtension(filename); + } + + // Return an empty stem. + return ""; +} + +std::string TextureUtils::GetOrGenerateTargetStem(const Texture &texture, + int index, + const std::string &suffix) { + // Return target stem from |texture| if there is one. + const std::string name = GetTargetStem(texture); + if (!name.empty()) { + return name; + } + + // Return target stem generated from |index| and |suffix|. + return "Texture" + std::to_string(index) + suffix; +} + +ImageFormat TextureUtils::GetTargetFormat(const Texture &texture) { + // Return format based on source image mime type. + return GetSourceFormat(texture); +} + +std::string TextureUtils::GetTargetExtension(const Texture &texture) { + return GetExtension(GetTargetFormat(texture)); +} + +ImageFormat TextureUtils::GetSourceFormat(const Texture &texture) { + // Try to get the extension based on source image mime type. + std::string extension = + LowercaseMimeTypeExtension(texture.source_image().mime_type()); + if (extension.empty() && !texture.source_image().filename().empty()) { + // Try to get the extension from the source image filename. + extension = LowercaseFileExtension(texture.source_image().filename()); + } + if (extension.empty()) { + // Default to png. + extension = "png"; + } + return GetFormat(extension); +} + +ImageFormat TextureUtils::GetFormat(const std::string &extension) { + if (extension == "png") { + return ImageFormat::PNG; + } else if (extension == "jpg" || extension == "jpeg") { + return ImageFormat::JPEG; + } else if (extension == "basis" || extension == "ktx2") { + return ImageFormat::BASIS; + } else if (extension == "webp") { + return ImageFormat::WEBP; + } + return ImageFormat::NONE; +} + +std::string TextureUtils::GetExtension(ImageFormat format) { + switch (format) { + case ImageFormat::PNG: + return "png"; + case ImageFormat::JPEG: + return "jpg"; + case ImageFormat::BASIS: + return "ktx2"; + case ImageFormat::WEBP: + return "webp"; + case ImageFormat::NONE: + default: + return ""; + } +} + +int TextureUtils::ComputeRequiredNumChannels( + const Texture &texture, const MaterialLibrary &material_library) { + // TODO(vytyaz): Consider a case where |texture| is not only used in OMR but + // also in other texture map types. + const auto mr_textures = TextureUtils::FindTextures( + TextureMap::METALLIC_ROUGHNESS, &material_library); + if (std::find(mr_textures.begin(), mr_textures.end(), &texture) == + mr_textures.end()) { + // Occlusion-only texture. + return 1; + } + // Occlusion-metallic-roughness texture. + return 3; +} + +std::vector TextureUtils::FindTextures( + const TextureMap::Type texture_type, + const MaterialLibrary *material_library) { + // Find textures with no duplicates. + std::unordered_set textures; + for (int i = 0; i < material_library->NumMaterials(); ++i) { + const TextureMap *const texture_map = + material_library->GetMaterial(i)->GetTextureMapByType(texture_type); + if (texture_map != nullptr && texture_map->texture() != nullptr) { + textures.insert(texture_map->texture()); + } + } + + // Return the textures as a vector. + std::vector result; + result.insert(result.end(), textures.begin(), textures.end()); + return result; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.h b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.h new file mode 100644 index 000000000..18d29950a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils.h @@ -0,0 +1,78 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_UTILS_H_ +#define DRACO_TEXTURE_TEXTURE_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/material/material_library.h" +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Helper class implementing various utilities operating on draco::Texture. +class TextureUtils { + public: + // Returns |texture| image stem (file basename without extension) based on the + // source image filename or an empty string when source image is not set. + static std::string GetTargetStem(const Texture &texture); + + // Returns |texture| image stem (file basename without extension) based on the + // source image filename or a name generated from |index| and |suffix| like + // "Texture5_BaseColor" when source image is not set. + static std::string GetOrGenerateTargetStem(const Texture &texture, int index, + const std::string &suffix); + + // Returns |texture| format based on compression settings, the source image + // mime type or the source image filename. + static ImageFormat GetTargetFormat(const Texture &texture); + + // Returns |texture| image file extension based on compression settings, the + // source image mime type or the source image filename. + static std::string GetTargetExtension(const Texture &texture); + + // Returns |texture| format based on source image mime type or the source + // image filename. + static ImageFormat GetSourceFormat(const Texture &texture); + + // Returns image format corresponding to a given image file |extension|. NONE + // is returned when |extension| is empty or unknown. + static ImageFormat GetFormat(const std::string &extension); + + // Returns image file extension corresponding to a given image |format|. Empty + // extension is returned when the |format| is NONE. + static std::string GetExtension(ImageFormat format); + + // Returns the number of channels required for encoding a |texture| from a + // given |material_library|, taking into account texture opacity and assuming + // that occlusion and metallic-roughness texture maps may share a texture. + // TODO(vytyaz): Move this and FindTextures() to MaterialLibrary class. + static int ComputeRequiredNumChannels( + const Texture &texture, const MaterialLibrary &material_library); + + static std::vector FindTextures( + const TextureMap::Type texture_type, + const MaterialLibrary *material_library); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_UTILS_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils_test.cc new file mode 100644 index 000000000..45873ea7a --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/texture/texture_utils_test.cc @@ -0,0 +1,163 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" +#include "draco/texture/color_utils.h" + +namespace { + +TEST(TextureUtilsTest, TestGetTargetNameForTextureLoadedFromFile) { + // Tests that correct target stem and format are returned by texture utils for + // texture loaded from image file (stem and format from source file). + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("fast.jpg")) + .value(); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), "fast"); + ASSERT_EQ(draco::TextureUtils::GetTargetExtension(*texture), "jpg"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetOrGenerateTargetStem(*texture, 5, "_Color"), + "fast"); +} + +TEST(TextureUtilsTest, TestGetTargetNameForNewTexture) { + // Tests that correct target stem and format are returned by texture utils for + // a newly created texture (empty stem and PNG image type by default). + std::unique_ptr texture(new draco::Texture()); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), ""); + ASSERT_EQ(draco::TextureUtils::GetOrGenerateTargetStem(*texture, 5, "_Color"), + "Texture5_Color"); + ASSERT_EQ(draco::TextureUtils::GetTargetExtension(*texture), "png"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::PNG); +} + +TEST(TextureUtilsTest, TestGetSourceFormat) { + // Tests that the source format is determined correctly for new textures and + // for textures loaded from file. + std::unique_ptr new_texture(new draco::Texture()); + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr png_texture, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png"))); + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr jpg_texture, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("fast.jpg"))); + + // Check source formats. + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*new_texture), + draco::ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*png_texture), + draco::ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*jpg_texture), + draco::ImageFormat::JPEG); + + // Remove the mime-type from the jpeg texture and ensure the source format is + // still detected properly based on the filename. + jpg_texture->source_image().set_mime_type(""); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*jpg_texture), + draco::ImageFormat::JPEG); +} + +TEST(TextureUtilsTest, TestGetFormat) { + typedef draco::ImageFormat ImageFormat; + ASSERT_EQ(draco::TextureUtils::GetFormat("png"), ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetFormat("jpg"), ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetFormat("jpeg"), ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetFormat("basis"), ImageFormat::BASIS); + ASSERT_EQ(draco::TextureUtils::GetFormat("ktx2"), ImageFormat::BASIS); + ASSERT_EQ(draco::TextureUtils::GetFormat("webp"), ImageFormat::WEBP); + ASSERT_EQ(draco::TextureUtils::GetFormat(""), ImageFormat::NONE); + ASSERT_EQ(draco::TextureUtils::GetFormat("bmp"), ImageFormat::NONE); +} + +TEST(TextureUtilsTest, TestGetExtension) { + typedef draco::ImageFormat ImageFormat; + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::PNG), "png"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::JPEG), "jpg"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::BASIS), "ktx2"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::WEBP), "webp"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::NONE), ""); +} + +TEST(TextureUtilsTest, TestComputeRequiredNumChannels) { + // Tests that the number of texture channels can be computed. Material library + // under test is created programmatically. + + // Load textures. + DRACO_ASSIGN_OR_ASSERT( + auto texture0, draco::ReadTextureFromFile( + draco::GetTestFileFullPath("fully_transparent.png"))); + ASSERT_NE(texture0, nullptr); + draco::Texture *texture0_ptr = texture0.get(); + DRACO_ASSIGN_OR_ASSERT( + auto texture1, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("squares.png"))); + ASSERT_NE(texture1, nullptr); + const draco::Texture *texture1_ptr = texture1.get(); + DRACO_ASSIGN_OR_ASSERT( + auto texture2, draco::ReadTextureFromFile( + draco::GetTestFileFullPath("fully_transparent.png"))); + ASSERT_NE(texture2, nullptr); + const draco::Texture *texture2_ptr = texture2.get(); + + // Compute number of channels for occlusion-only texture. + draco::MaterialLibrary library; + draco::Material *const material0 = library.MutableMaterial(0); + material0->SetTextureMap(std::move(texture0), + draco::TextureMap::AMBIENT_OCCLUSION, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 1); + + // Compute number of channels for occlusion-only texture with MR present but + // not using the same texture. + draco::Material *const material1 = library.MutableMaterial(1); + material1->SetTextureMap(std::move(texture1), + draco::TextureMap::METALLIC_ROUGHNESS, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 1); + + // Compute number of channels for metallic-roughness texture. + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture1_ptr, library), + 3); + + // Compute number of channels texture that is used for occlusin map in one + // material and also shared with metallic-roughness map in another material. + draco::Material *const material2 = library.MutableMaterial(2); + DRACO_ASSERT_OK(material2->SetTextureMap( + texture0_ptr, draco::TextureMap::METALLIC_ROUGHNESS, 0)); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 3); + + // Compute number of channels for non-opaque texture. + material0->SetTextureMap(std::move(texture2), draco::TextureMap::COLOR, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture2_ptr, library), + 4); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_decoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_decoder.cc index 610709d62..cf5f18094 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_decoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_decoder.cc @@ -20,6 +20,7 @@ #include "draco/io/obj_encoder.h" #include "draco/io/parser_utils.h" #include "draco/io/ply_encoder.h" +#include "draco/io/stl_encoder.h" namespace { @@ -126,7 +127,6 @@ int main(int argc, char **argv) { } // Save the decoded geometry into a file. - // TODO(fgalligan): Change extension code to look for '.'. const std::string extension = draco::parser::ToLower( options.output.size() >= 4 ? options.output.substr(options.output.size() - 4) @@ -140,7 +140,7 @@ int main(int argc, char **argv) { return -1; } } else { - if (!obj_encoder.EncodeToFile(*pc.get(), options.output)) { + if (!obj_encoder.EncodeToFile(*pc, options.output)) { printf("Failed to store the decoded point cloud as OBJ.\n"); return -1; } @@ -153,13 +153,25 @@ int main(int argc, char **argv) { return -1; } } else { - if (!ply_encoder.EncodeToFile(*pc.get(), options.output)) { + if (!ply_encoder.EncodeToFile(*pc, options.output)) { printf("Failed to store the decoded point cloud as PLY.\n"); return -1; } } + } else if (extension == ".stl") { + draco::StlEncoder stl_encoder; + if (mesh) { + draco::Status s = stl_encoder.EncodeToFile(*mesh, options.output); + if (s.code() != draco::Status::OK) { + printf("Failed to store the decoded mesh as STL.\n"); + return -1; + } + } else { + printf("Can't store a point cloud as STL.\n"); + return -1; + } } else { - printf("Invalid extension of the output file. Use either .ply or .obj.\n"); + printf("Invalid output file extension. Use .obj .ply or .stl.\n"); return -1; } printf("Decoded geometry saved to %s (%" PRId64 " ms to decode)\n", diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_encoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_encoder.cc index 7e3632d7d..ec639453b 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_encoder.cc +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_encoder.cc @@ -15,7 +15,9 @@ #include #include +#include "draco/compression/config/compression_shared.h" #include "draco/compression/encode.h" +#include "draco/compression/expert_encode.h" #include "draco/core/cycle_timer.h" #include "draco/io/file_utils.h" #include "draco/io/mesh_io.h" @@ -35,6 +37,7 @@ struct Options { int generic_quantization_bits; bool generic_deleted; int compression_level; + bool preserve_polygons; bool use_metadata; std::string input; std::string output; @@ -50,6 +53,7 @@ Options::Options() generic_quantization_bits(8), generic_deleted(false), compression_level(7), + preserve_polygons(false), use_metadata(false) {} void Usage() { @@ -83,6 +87,11 @@ void Usage() { printf( " --metadata use metadata to encode extra information in " "mesh files.\n"); + // Mesh with polygonal faces loaded from OBJ format is converted to triangular + // mesh and polygon reconstruction information is encoded into a new generic + // attribute. + printf(" -preserve_polygons encode polygon info as an attribute.\n"); + printf( "\nUse negative quantization values to skip the specified attribute\n"); } @@ -138,12 +147,12 @@ void PrintOptions(const draco::PointCloud &pc, const Options &options) { } int EncodePointCloudToFile(const draco::PointCloud &pc, const std::string &file, - draco::Encoder *encoder) { + draco::ExpertEncoder *encoder) { draco::CycleTimer timer; // Encode the geometry. draco::EncoderBuffer buffer; timer.Start(); - const draco::Status status = encoder->EncodePointCloudToBuffer(pc, &buffer); + const draco::Status status = encoder->EncodeToBuffer(&buffer); if (!status.ok()) { printf("Failed to encode the point cloud.\n"); printf("%s\n", status.error_msg()); @@ -162,12 +171,12 @@ int EncodePointCloudToFile(const draco::PointCloud &pc, const std::string &file, } int EncodeMeshToFile(const draco::Mesh &mesh, const std::string &file, - draco::Encoder *encoder) { + draco::ExpertEncoder *encoder) { draco::CycleTimer timer; // Encode the geometry. draco::EncoderBuffer buffer; timer.Start(); - const draco::Status status = encoder->EncodeMeshToBuffer(mesh, &buffer); + const draco::Status status = encoder->EncodeToBuffer(&buffer); if (!status.ok()) { printf("Failed to encode the mesh.\n"); printf("%s\n", status.error_msg()); @@ -249,6 +258,8 @@ int main(int argc, char **argv) { ++i; } else if (!strcmp("--metadata", argv[i])) { options.use_metadata = true; + } else if (!strcmp("-preserve_polygons", argv[i])) { + options.preserve_polygons = true; } } if (argc < 3 || options.input.empty()) { @@ -259,8 +270,10 @@ int main(int argc, char **argv) { std::unique_ptr pc; draco::Mesh *mesh = nullptr; if (!options.is_point_cloud) { - auto maybe_mesh = - draco::ReadMeshFromFile(options.input, options.use_metadata); + draco::Options load_options; + load_options.SetBool("use_metadata", options.use_metadata); + load_options.SetBool("preserve_polygons", options.preserve_polygons); + auto maybe_mesh = draco::ReadMeshFromFile(options.input, load_options); if (!maybe_mesh.ok()) { printf("Failed loading the input mesh: %s.\n", maybe_mesh.status().error_msg()); @@ -350,14 +363,36 @@ int main(int argc, char **argv) { options.output = options.input + ".drc"; } - PrintOptions(*pc.get(), options); + PrintOptions(*pc, options); + + const bool input_is_mesh = mesh && mesh->num_faces() > 0; + + // Convert to ExpertEncoder that allows us to set per-attribute options. + std::unique_ptr expert_encoder; + if (input_is_mesh) { + expert_encoder.reset(new draco::ExpertEncoder(*mesh)); + } else { + expert_encoder.reset(new draco::ExpertEncoder(*pc)); + } + expert_encoder->Reset(encoder.CreateExpertEncoderOptions(*pc)); + + // Check if there is an attribute that stores polygon edges. If so, we disable + // the default prediction scheme for the attribute as it actually makes the + // compression worse. + const int poly_att_id = + pc->GetAttributeIdByMetadataEntry("name", "added_edges"); + if (poly_att_id != -1) { + expert_encoder->SetAttributePredictionScheme( + poly_att_id, draco::PredictionSchemeMethod::PREDICTION_NONE); + } int ret = -1; - const bool input_is_mesh = mesh && mesh->num_faces() > 0; - if (input_is_mesh) - ret = EncodeMeshToFile(*mesh, options.output, &encoder); - else - ret = EncodePointCloudToFile(*pc.get(), options.output, &encoder); + + if (input_is_mesh) { + ret = EncodeMeshToFile(*mesh, options.output, expert_encoder.get()); + } else { + ret = EncodePointCloudToFile(*pc, options.output, expert_encoder.get()); + } if (ret != -1 && options.compression_level < 10) { printf( diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder.cc new file mode 100644 index 000000000..ad0f27714 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder.cc @@ -0,0 +1,130 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include +#include + +#include "draco/core/cycle_timer.h" +#include "draco/core/status.h" +#include "draco/draco_features.h" +#include "draco/texture/texture_utils.h" +#include "draco/tools/draco_transcoder_lib.h" + +namespace { + +// TODO(fgalligan): Add support for no compression to the transcoder lib. +void Usage() { + // TODO(b/204212351): Revisit using a raw string literal here for readability. + printf("Usage: draco_transcoder [options] -i input -o output\n\n"); + printf("Main options:\n"); + printf(" -h | -? show help.\n"); + printf(" -i input file name.\n"); + printf(" -o output file name.\n"); + printf(" -qp quantization bits for the position attribute, "); + printf("default=11.\n"); + printf(" -qt quantization bits for the texture coordinate "); + printf("attribute, default=10.\n"); + printf(" -qn quantization bits for the normal vector attribute"); + printf(", default=8.\n"); + printf(" -qc quantization bits for the color attribute, "); + printf("default=8.\n"); + printf(" -qtg quantization bits for the tangent attribute, "); + printf("default=8.\n"); + printf(" -qw quantization bits for the weight attribute, "); + printf("default=8.\n"); + printf(" -qg quantization bits for any generic attribute, "); + printf("default=8.\n"); + + printf("\nBoolean options may be negated by prefixing 'no'.\n"); +} + +int StringToInt(const std::string &s) { + char *end; + return strtol(s.c_str(), &end, 10); // NOLINT +} + +bool MatchesBooleanOption(const std::string &option, const std::string &value) { + const std::string opt = "-" + option; + const std::string noopt = "-no" + option; + return value == opt || value == noopt; +} + +draco::Status TranscodeFile( + const draco::DracoTranscoder::FileOptions &file_options, + const draco::DracoTranscodingOptions &transcode_options) { + draco::CycleTimer timer; + timer.Start(); + DRACO_ASSIGN_OR_RETURN(std::unique_ptr dt, + draco::DracoTranscoder::Create(transcode_options)); + + DRACO_RETURN_IF_ERROR(dt->Transcode(file_options)); + timer.Stop(); + printf("Transcode\t%s\t%" PRId64 "\n", file_options.input_filename.c_str(), + timer.GetInMs()); + + return draco::OkStatus(); +} + +} // anonymous namespace + +int main(int argc, char **argv) { + draco::DracoTranscoder::FileOptions file_options; + draco::DracoTranscodingOptions transcode_options; + const int argc_check = argc - 1; + + for (int i = 1; i < argc; ++i) { + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return 0; + } else if (!strcmp("-i", argv[i]) && i < argc_check) { + file_options.input_filename = argv[++i]; + } else if (!strcmp("-o", argv[i]) && i < argc_check) { + file_options.output_filename = argv[++i]; + } else if (!strcmp("-qp", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_position.SetQuantizationBits( + StringToInt(argv[++i])); + } else if (!strcmp("-qt", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_tex_coord = + StringToInt(argv[++i]); + } else if (!strcmp("-qn", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_normal = + StringToInt(argv[++i]); + } else if (!strcmp("-qc", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_color = + StringToInt(argv[++i]); + } else if (!strcmp("-qtg", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_tangent = + StringToInt(argv[++i]); + } else if (!strcmp("-qw", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_weight = + StringToInt(argv[++i]); + } else if (!strcmp("-qg", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_generic = + StringToInt(argv[++i]); + } + } + if (argc < 3 || file_options.input_filename.empty() || + file_options.output_filename.empty()) { + Usage(); + return -1; + } + + const draco::Status status = TranscodeFile(file_options, transcode_options); + if (!status.ok()) { + printf("Failed\t%s\t%s\n", file_options.input_filename.c_str(), + status.error_msg()); + return -1; + } + return 0; +} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.cc new file mode 100644 index 000000000..b0bc43843 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.cc @@ -0,0 +1,86 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/tools/draco_transcoder_lib.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/io/scene_io.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +DracoTranscoder::DracoTranscoder() {} + +StatusOr> DracoTranscoder::Create( + const DracoTranscodingOptions &options) { + DRACO_RETURN_IF_ERROR(options.geometry.Check()); + std::unique_ptr dt(new DracoTranscoder()); + dt->transcoding_options_ = options; + return dt; +} + +StatusOr> DracoTranscoder::Create( + const DracoCompressionOptions &options) { + DracoTranscodingOptions new_options; + new_options.geometry = options; + return Create(new_options); +} + +Status DracoTranscoder::Transcode(const FileOptions &file_options) { + DRACO_RETURN_IF_ERROR(ReadScene(file_options)); + DRACO_RETURN_IF_ERROR(CompressScene()); + DRACO_RETURN_IF_ERROR(WriteScene(file_options)); + return OkStatus(); +} + +Status DracoTranscoder::ReadScene(const FileOptions &file_options) { + if (file_options.input_filename.empty()) { + return Status(Status::DRACO_ERROR, "Input filename is empty."); + } else if (file_options.output_filename.empty()) { + return Status(Status::DRACO_ERROR, "Output filename is empty."); + } + DRACO_ASSIGN_OR_RETURN(scene_, + ReadSceneFromFile(file_options.input_filename)); + return OkStatus(); +} + +Status DracoTranscoder::WriteScene(const FileOptions &file_options) { + if (!file_options.output_bin_filename.empty() && + !file_options.output_resource_directory.empty()) { + DRACO_RETURN_IF_ERROR(gltf_encoder_.EncodeFile( + *scene_, file_options.output_filename, file_options.output_bin_filename, + file_options.output_resource_directory)); + } else if (!file_options.output_bin_filename.empty()) { + DRACO_RETURN_IF_ERROR( + gltf_encoder_.EncodeFile(*scene_, file_options.output_filename, + file_options.output_bin_filename)); + } else { + DRACO_RETURN_IF_ERROR( + gltf_encoder_.EncodeFile(*scene_, file_options.output_filename)); + } + return OkStatus(); +} + +Status DracoTranscoder::CompressScene() { + // Apply geometry compression settings to all scene meshes. + SceneUtils::SetDracoCompressionOptions(&transcoding_options_.geometry, + scene_.get()); + return OkStatus(); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.h b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.h new file mode 100644 index 000000000..c94d19de7 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib.h @@ -0,0 +1,103 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ +#define DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/compression/draco_compression_options.h" +#include "draco/core/options.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/image_compression_options.h" + +namespace draco { + +// Struct to hold Draco transcoding options. +struct DracoTranscodingOptions { + DracoTranscodingOptions() {} + + // Options used when geometry compression optimization is disabled. + DracoCompressionOptions geometry; +}; + +// Class that supports input of glTF (and some simple USD) files, encodes +// them with Draco compression, and outputs glTF Draco compressed files. +// +// glTF supported extensions: +// Input and Output: +// KHR_draco_mesh_compression. http://shortn/_L5tPQqdwWf +// KHR_materials_unlit. http://shortn/_3eaDLoIGam +// KHR_texture_transform. http://shortn/_PORWgVTEe8 +// +// glTF unsupported features: +// Input and Output: +// Morph targets. http://shortn/_zE5DLw8a9B +// Sparse accessors. http://shortn/_h3FwbzQl4f +// KHR_lights_punctual. http://shortn/_nzGk80wKtK +// KHR_materials_pbrSpecularGlossiness. http://shortn/_iz0VC6dIKe +// All vendor extensions. +class DracoTranscoder { + public: + struct FileOptions { + std::string input_filename; // Must be non-empty. + std::string output_filename; // Must be non-empty. + std::string output_bin_filename = ""; + std::string output_resource_directory = ""; + }; + + DracoTranscoder(); + + // Creates a DracoTranscoder object. |options| sets the compression options + // used in the Encode function. + static StatusOr> Create( + const DracoTranscodingOptions &options); + + // Deprecated. + // TODO(fgalligan): Remove when function is not being used anymore. + static StatusOr> Create( + const DracoCompressionOptions &options); + + // Encodes the input with Draco compression using the compression options + // passed in the Create function. The recommended use case is to create a + // transcoder once and call Transcode for multiple files. + Status Transcode(const FileOptions &file_options); + + private: + // Read scene from file. + Status ReadScene(const FileOptions &file_options); + + // Write scene to file. + Status WriteScene(const FileOptions &file_options); + + // Apply compression settings to the scene. + Status CompressScene(); + + private: + GltfEncoder gltf_encoder_; + + // The scene being transcoded. + std::unique_ptr scene_; + + // Copy of the transcoding options passed into the Create function. + DracoTranscodingOptions transcoding_options_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc new file mode 100644 index 000000000..a87a158e0 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc @@ -0,0 +1,172 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/tools/draco_transcoder_lib.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" + +// Tests encoding a .gltf file with default Draco compression. +TEST(DracoTranscoderTest, DefaultDracoCompression) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("test.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Tests setting the output glTF .bin name. +TEST(DracoTranscoderTest, TestBinName) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("different_name.bin"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + file_options.output_bin_filename = output_bin_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Tests setting the output glTF resource directory. +TEST(DracoTranscoderTest, TestResourceDirName) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("another_name.bin"); + const std::string output_resource_directory = + draco::GetTestTempFileFullPath("res/other_files"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + file_options.output_bin_filename = output_bin_filename; + file_options.output_resource_directory = output_resource_directory; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); + + const std::string res_dir_png_filename = draco::GetTestTempFileFullPath( + "res/other_files/sphere_Texture0_Normal.png"); + const size_t output_png_size = draco::GetFileSize(res_dir_png_filename); + ASSERT_GT(output_png_size, 0); +} + +// Tests creating one transcoder to encode multiple files. +TEST(DracoTranscoderTest, EncodeMultipleFiles) { + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = draco::GetTestFileFullPath("sphere.gltf"); + file_options.output_filename = draco::GetTestTempFileFullPath("first.gltf"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t first_bin_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("first.bin")); + ASSERT_GT(first_bin_size, 0); + + file_options.input_filename = + draco::GetTestFileFullPath("CesiumMan/glTF/CesiumMan.gltf"); + file_options.output_filename = draco::GetTestTempFileFullPath("second.gltf"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t second_bin_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("second.bin")); + ASSERT_GT(second_bin_size, 0); +} + +// Tests using glTF binary as input. +TEST(DracoTranscoderTest, SimpleGlbInput) { + const std::string input_name = "Box/glTF_Binary/Box.glb"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("test.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Simple test to check glb input and setting smaller position quantizations +// outputs a smaller file overall. +TEST(DracoTranscoderTest, TestPositionQuantization) { + const std::string input_name = + "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + + draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = draco::GetTestTempFileFullPath("first.glb"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t first_glb_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("first.glb")); + + options.geometry.quantization_position.SetQuantizationBits(10); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt2, + draco::DracoTranscoder::Create(options)); + file_options.output_filename = draco::GetTestTempFileFullPath("second.glb"); + DRACO_ASSERT_OK(dt2->Transcode(file_options)); + const size_t second_glb_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("second.glb")); + ASSERT_GT(first_glb_size, second_glb_size); +} + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/fuzz/build.sh b/Engine/lib/assimp/contrib/draco/src/draco/tools/fuzz/build.sh index bbeb10591..3e48409fc 100644 --- a/Engine/lib/assimp/contrib/draco/src/draco/tools/fuzz/build.sh +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/fuzz/build.sh @@ -19,7 +19,7 @@ cmake $SRC/draco # The draco_decoder and draco_encoder binaries don't build nicely with OSS-Fuzz # options, so just build the Draco shared libraries. -make -j$(nproc) draco +make -j$(nproc) # build fuzzers for fuzzer in $(find $SRC/draco/src/draco/tools/fuzz -name '*.cc'); do diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/CMakeLists.txt b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/CMakeLists.txt new file mode 100644 index 000000000..800dc9c9b --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +cmake_minimum_required(VERSION 3.12) +project(install_test C CXX) + +include(GNUInstallDirs) + +find_package(draco REQUIRED CONFIG) + +add_executable(install_check main.cc) +target_link_libraries(install_check PRIVATE draco::draco) + +install(TARGETS install_check DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/main.cc b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/main.cc new file mode 100644 index 000000000..e76793b64 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/main.cc @@ -0,0 +1,44 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This program is used to test the installed version of Draco. It does just +// enough to confirm that an application using Draco can compile and link +// against an installed version of Draco without errors. It does not perform +// any sort of library tests. + +#include +#include + +#include "draco/core/decoder_buffer.h" + +#if defined DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" +#include "draco/scene/scene_utils.h" +#endif + +int main(int /*argc*/, char** /*argv*/) { + std::vector empty_buffer; + draco::DecoderBuffer buffer; + buffer.Init(empty_buffer.data(), empty_buffer.size()); + +#if defined DRACO_TRANSCODER_SUPPORTED + draco::Scene empty_scene; + const int num_meshes = empty_scene.NumMeshes(); + (void)num_meshes; +#endif + + printf("Partial sanity test passed.\n"); + return 0; +} diff --git a/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/test.py b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/test.py new file mode 100644 index 000000000..8612e70b5 --- /dev/null +++ b/Engine/lib/assimp/contrib/draco/src/draco/tools/install_test/test.py @@ -0,0 +1,456 @@ +#!/usr/bin/python3 +# +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests installations of the Draco library. + +Builds the library in shared and static configurations on the current host +system, and then confirms that a simple test application can link in both +configurations. +""" + +import argparse +import multiprocessing +import os +import pathlib +import shlex +import shutil +import subprocess +import sys + +# CMake executable. +CMAKE = shutil.which('cmake') + +# List of generators available in the current CMake executable. +CMAKE_AVAILABLE_GENERATORS = [] + +# List of variable defs to be passed through to CMake via its -D argument. +CMAKE_DEFINES = [] + +# CMake builds use the specified generator. +CMAKE_GENERATOR = None + +# Enable the transcoder before running tests (sets DRACO_TRANSCODER_SUPPORTED +# and builds transcoder support dependencies). +ENABLE_TRANSCODER = False + +# The Draco tree that this script uses. +DRACO_SOURCES_PATH = os.path.abspath(os.path.join('..', '..', '..', '..')) + +# Path to this script and the rest of the test project files. +TEST_SOURCES_PATH = os.path.dirname(os.path.abspath(__file__)) + +# The Draco build directories. +DRACO_SHARED_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_draco_build_shared') +DRACO_STATIC_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_draco_build_static') + +# The Draco install roots. +DRACO_SHARED_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_draco_install_shared') +DRACO_STATIC_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_draco_install_static') + +DRACO_SHARED_INSTALL_BIN_PATH = os.path.join(DRACO_SHARED_INSTALL_PATH, 'bin') + +DRACO_SHARED_INSTALL_LIB_PATH = os.path.join(DRACO_SHARED_INSTALL_PATH, 'lib') + +# Argument for -j when using make, or -m when using Visual Studio. Number of +# build jobs. +NUM_PROCESSES = multiprocessing.cpu_count() - 1 + +# The test project build directories. +TEST_SHARED_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_test_build_shared') +TEST_STATIC_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_test_build_static') + +# The test project install directories. +TEST_SHARED_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_test_install_shared') +TEST_STATIC_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_test_install_static') + +# Show configuration and build output. +VERBOSE = False + + +def cmake_get_available_generators(): + """Returns list of generators available in current CMake executable.""" + result = run_process_and_capture_output(f'{CMAKE} --help') + + if result[0] != 0: + raise Exception(f'cmake --help failed, exit code: {result[0]}\n{result[1]}') + + help_text = result[1].splitlines() + generators_start_index = help_text.index('Generators') + 3 + generators_text = help_text[generators_start_index::] + + generators = [] + for gen in generators_text: + gen = gen.split('=')[0].strip().replace('* ', '') + + if gen and gen[0] != '=': + generators.append(gen) + + return generators + + +def cmake_get_generator(): + """Returns the CMake generator from CMakeCache.txt in the current dir.""" + cmake_cache_file_path = os.path.join(os.getcwd(), 'CMakeCache.txt') + cmake_cache_text = '' + with open(cmake_cache_file_path, 'r') as cmake_cache_file: + cmake_cache_text = cmake_cache_file.read() + + if not cmake_cache_text: + raise FileNotFoundError(f'{cmake_cache_file_path} missing or empty.') + + generator = '' + for line in cmake_cache_text.splitlines(): + if line.startswith('CMAKE_GENERATOR:INTERNAL='): + generator = line.split('=')[1] + + return generator + + +def run_process_and_capture_output(cmd, env=None): + """Runs |cmd| as a child process. + + Returns process exit code and output. Streams process output to stdout when + VERBOSE is true. + + Args: + cmd: String containing the command to execute. + env: Optional dict of environment variables. + + Returns: + Tuple of exit code and output. + """ + if not cmd: + raise ValueError('run_process_and_capture_output requires cmd argument.') + + if os.name == 'posix': + # On posix systems subprocess.Popen will treat |cmd| as the program name + # when it is passed as a string. Unconditionally split the command so + # callers don't need to care about this detail. + cmd = shlex.split(cmd) + + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) + + if VERBOSE: + print('COMMAND output:') + + stdout = '' + for line in iter(proc.stdout.readline, b''): + decoded_line = line.decode('utf-8') + if VERBOSE: + sys.stdout.write(decoded_line) + sys.stdout.flush() + stdout += decoded_line + + # Wait for the process to exit so that the exit code is available. + proc.wait() + return [proc.returncode, stdout] + + +def create_output_directories(): + """Creates the build output directores for the test.""" + pathlib.Path(DRACO_SHARED_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(DRACO_STATIC_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(TEST_SHARED_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(TEST_STATIC_BUILD_PATH).mkdir(parents=True, exist_ok=True) + + +def cleanup(): + """Removes the build output directories from the test.""" + shutil.rmtree(DRACO_SHARED_BUILD_PATH) + shutil.rmtree(DRACO_STATIC_BUILD_PATH) + shutil.rmtree(DRACO_SHARED_INSTALL_PATH) + shutil.rmtree(DRACO_STATIC_INSTALL_PATH) + shutil.rmtree(TEST_SHARED_BUILD_PATH) + shutil.rmtree(TEST_STATIC_BUILD_PATH) + shutil.rmtree(TEST_SHARED_INSTALL_PATH) + shutil.rmtree(TEST_STATIC_INSTALL_PATH) + + +def cmake_configure(source_path, cmake_args=None): + """Configures a CMake build.""" + command = f'{CMAKE} {source_path}' + + if CMAKE_GENERATOR: + if ' ' in CMAKE_GENERATOR: + command += f' -G "{CMAKE_GENERATOR}"' + else: + command += f' -G {CMAKE_GENERATOR}' + + if cmake_args: + for arg in cmake_args: + command += f' {arg}' + + if CMAKE_DEFINES: + for arg in CMAKE_DEFINES: + command += f' -D{arg}' + + if VERBOSE: + print(f'CONFIGURE command:\n{command}') + + result = run_process_and_capture_output(command) + + if result[0] != 0: + raise Exception(f'CONFIGURE failed!\nexit_code: {result[0]}\n{result[1]}') + + +def cmake_build(cmake_args=None, build_args=None): + """Runs a CMake build.""" + command = f'{CMAKE} --build .' + + if cmake_args: + for arg in cmake_args: + command += f' {arg}' + + if not build_args: + build_args = [] + + generator = cmake_get_generator() + if generator.endswith('Makefiles'): + build_args.append(f'-j {NUM_PROCESSES}') + elif generator.startswith('Visual'): + build_args.append(f'-m:{NUM_PROCESSES}') + + if build_args: + command += ' --' + for arg in build_args: + command += f' {arg}' + + if VERBOSE: + print(f'BUILD command:\n{command}') + + result = run_process_and_capture_output(f'{command}') + + if result[0] != 0: + raise Exception(f'BUILD failed!\nexit_code: {result[0]}\n{result[1]}') + + +def run_install_check(install_path): + """Runs the install_check program.""" + cmd = os.path.join(install_path, 'bin', 'install_check') + if VERBOSE: + print(f'RUN command: {cmd}') + + result = run_process_and_capture_output( + cmd, + # On Windows, add location of draco.dll into PATH env var + {'PATH': DRACO_SHARED_INSTALL_BIN_PATH + os.pathsep + os.environ['PATH']}, + ) + if result[0] != 0: + raise Exception( + f'install_check run failed!\nexit_code: {result[0]}\n{result[1]}') + + +def build_and_install_transcoder_dependencies(): + """Builds and installs Draco dependencies for transcoder enabled builds.""" + orig_dir = os.getcwd() + + # The Eigen CMake build in the release Draco has pinned is, to put it mildly, + # user unfriendly. Instead of wasting time trying to integrate it here, just + # shutil.copytree() everything in $eigen_submodule_path to + # $CMAKE_INSTALL_PREFIX/include/Eigen. + # Eigen claims to be header-only, so this should be adequate for Draco's + # needs here. + eigen_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'eigen', 'Eigen') + + # "Install" Eigen for the shared install root. + eigen_install_path = os.path.join( + DRACO_SHARED_INSTALL_PATH, 'include', 'Eigen') + shutil.copytree(src=eigen_submodule_path, dst=eigen_install_path) + + # "Install" Eigen for the static install root. + eigen_install_path = os.path.join( + DRACO_STATIC_INSTALL_PATH, 'include', 'Eigen') + shutil.copytree(src=eigen_submodule_path, dst=eigen_install_path) + + # Build and install gulrak/filesystem for shared and static configurations. + # Note that this is basically running gulrak/filesystem's CMake build as an + # install script. + fs_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'filesystem') + + # Install gulrak/filesystem in the shared draco install root. + fs_shared_build = os.path.join(DRACO_SHARED_BUILD_PATH, '_fs') + pathlib.Path(fs_shared_build).mkdir(parents=True, exist_ok=True) + os.chdir(fs_shared_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=ON') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_TESTING=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=fs_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Install gulrak/filesystem in the shared draco install root. + fs_static_build = os.path.join(DRACO_STATIC_BUILD_PATH, '_fs') + pathlib.Path(fs_static_build).mkdir(parents=True, exist_ok=True) + os.chdir(fs_static_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_TESTING=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=fs_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Build and install TinyGLTF for shared and static configurations. + # Note, as above, that this is basically running TinyGLTF's CMake build as an + # install script. + tinygltf_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'tinygltf') + + # Install TinyGLTF in the shared draco install root. + tinygltf_shared_build = os.path.join(DRACO_SHARED_BUILD_PATH, '_TinyGLTF') + pathlib.Path(tinygltf_shared_build).mkdir(parents=True, exist_ok=True) + os.chdir(tinygltf_shared_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DTINYGLTF_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=tinygltf_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Install TinyGLTF in the static draco install root. + tinygltf_static_build = os.path.join(DRACO_STATIC_BUILD_PATH, '_TinyGLTF') + pathlib.Path(tinygltf_static_build).mkdir(parents=True, exist_ok=True) + os.chdir(tinygltf_static_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_STATIC_INSTALL_PATH}') + cmake_args.append('-DTINYGLTF_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=tinygltf_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + os.chdir(orig_dir) + + +def build_and_install_draco(): + """Builds Draco in shared and static configurations.""" + orig_dir = os.getcwd() + + if ENABLE_TRANSCODER: + build_and_install_transcoder_dependencies() + + # Build and install Draco in shared library config for the current host + # machine. + os.chdir(DRACO_SHARED_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=ON') + if ENABLE_TRANSCODER: + cmake_args.append('-DDRACO_TRANSCODER_SUPPORTED=ON') + cmake_configure(source_path=DRACO_SOURCES_PATH, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Build and install Draco in the static config for the current host machine. + os.chdir(DRACO_STATIC_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_STATIC_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=OFF') + if ENABLE_TRANSCODER: + cmake_args.append('-DDRACO_TRANSCODER_SUPPORTED=ON') + cmake_configure(source_path=DRACO_SOURCES_PATH, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + os.chdir(orig_dir) + + +def build_test_project(): + """Builds the test application in shared and static configurations.""" + orig_dir = os.getcwd() + + # Configure the test project against draco shared and build it. + os.chdir(TEST_SHARED_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={TEST_SHARED_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_PREFIX_PATH={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_INSTALL_RPATH={DRACO_SHARED_INSTALL_LIB_PATH}') + cmake_configure(source_path=f'{TEST_SOURCES_PATH}', cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + run_install_check(TEST_SHARED_INSTALL_PATH) + + # Configure the test project against draco static and build it. + os.chdir(TEST_STATIC_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={TEST_STATIC_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_PREFIX_PATH={DRACO_STATIC_INSTALL_PATH}') + cmake_configure(source_path=f'{TEST_SOURCES_PATH}', cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + run_install_check(TEST_STATIC_INSTALL_PATH) + + os.chdir(orig_dir) + + +def test_draco_install(): + create_output_directories() + build_and_install_draco() + build_test_project() + cleanup() + + +if __name__ == '__main__': + CMAKE_AVAILABLE_GENERATORS = cmake_get_available_generators() + + parser = argparse.ArgumentParser() + parser.add_argument( + '-G', '--generator', help='CMake builds use the specified generator.') + parser.add_argument( + '-D', '--cmake_define', + action='append', + help='Passes argument through to CMake as a CMake variable via cmake -D.') + parser.add_argument( + '-t', '--with_transcoder', + action='store_true', + help='Run tests with Draco transcoder support enabled.') + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='Show configuration and build output.') + args = parser.parse_args() + + if args.cmake_define: + CMAKE_DEFINES = args.cmake_define + if args.generator: + CMAKE_GENERATOR = args.generator + if args.verbose: + VERBOSE = True + if args.with_transcoder: + ENABLE_TRANSCODER = True + + if VERBOSE: + print(f'CMAKE={CMAKE}') + print(f'CMAKE_DEFINES={CMAKE_DEFINES}') + print(f'CMAKE_GENERATOR={CMAKE_GENERATOR}') + print(f'CMAKE_AVAILABLE_GENERATORS={CMAKE_AVAILABLE_GENERATORS}') + print(f'ENABLE_TRANSCODER={ENABLE_TRANSCODER}') + print(f'DRACO_SOURCES_PATH={DRACO_SOURCES_PATH}') + print(f'DRACO_SHARED_BUILD_PATH={DRACO_SHARED_BUILD_PATH}') + print(f'DRACO_STATIC_BUILD_PATH={DRACO_STATIC_BUILD_PATH}') + print(f'DRACO_SHARED_INSTALL_PATH={DRACO_SHARED_INSTALL_PATH}') + print(f'DRACO_STATIC_INSTALL_PATH={DRACO_STATIC_INSTALL_PATH}') + print(f'NUM_PROCESSES={NUM_PROCESSES}') + print(f'TEST_SHARED_BUILD_PATH={TEST_SHARED_BUILD_PATH}') + print(f'TEST_STATIC_BUILD_PATH={TEST_STATIC_BUILD_PATH}') + print(f'TEST_SOURCES_PATH={TEST_SOURCES_PATH}') + print(f'VERBOSE={VERBOSE}') + + if CMAKE_GENERATOR and CMAKE_GENERATOR not in CMAKE_AVAILABLE_GENERATORS: + raise ValueError(f'CMake generator unavailable: {CMAKE_GENERATOR}.') + + test_draco_install() diff --git a/Engine/lib/assimp/contrib/googletest/.clang-format b/Engine/lib/assimp/contrib/googletest/.clang-format new file mode 100644 index 000000000..5b9bfe6d2 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/.clang-format @@ -0,0 +1,4 @@ +# Run manually to reformat a file: +# clang-format -i --style=file +Language: Cpp +BasedOnStyle: Google diff --git a/Engine/lib/assimp/contrib/googletest/BUILD.bazel b/Engine/lib/assimp/contrib/googletest/BUILD.bazel new file mode 100644 index 000000000..b1e3b7fba --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/BUILD.bazel @@ -0,0 +1,219 @@ +# Copyright 2017 Google Inc. +# All Rights Reserved. +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Bazel Build for Google C++ Testing Framework(Google Test) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +config_setting( + name = "qnx", + constraint_values = ["@platforms//os:qnx"], +) + +config_setting( + name = "windows", + constraint_values = ["@platforms//os:windows"], +) + +config_setting( + name = "freebsd", + constraint_values = ["@platforms//os:freebsd"], +) + +config_setting( + name = "openbsd", + constraint_values = ["@platforms//os:openbsd"], +) + +config_setting( + name = "msvc_compiler", + flag_values = { + "@bazel_tools//tools/cpp:compiler": "msvc-cl", + }, + visibility = [":__subpackages__"], +) + +config_setting( + name = "has_absl", + values = {"define": "absl=1"}, +) + +# Library that defines the FRIEND_TEST macro. +cc_library( + name = "gtest_prod", + hdrs = ["googletest/include/gtest/gtest_prod.h"], + includes = ["googletest/include"], +) + +# Google Test including Google Mock +cc_library( + name = "gtest", + srcs = glob( + include = [ + "googletest/src/*.cc", + "googletest/src/*.h", + "googletest/include/gtest/**/*.h", + "googlemock/src/*.cc", + "googlemock/include/gmock/**/*.h", + ], + exclude = [ + "googletest/src/gtest-all.cc", + "googletest/src/gtest_main.cc", + "googlemock/src/gmock-all.cc", + "googlemock/src/gmock_main.cc", + ], + ), + hdrs = glob([ + "googletest/include/gtest/*.h", + "googlemock/include/gmock/*.h", + ]), + copts = select({ + ":qnx": [], + ":windows": [], + "//conditions:default": ["-pthread"], + }), + defines = select({ + ":has_absl": ["GTEST_HAS_ABSL=1"], + "//conditions:default": [], + }), + features = select({ + ":windows": ["windows_export_all_symbols"], + "//conditions:default": [], + }), + includes = [ + "googlemock", + "googlemock/include", + "googletest", + "googletest/include", + ], + linkopts = select({ + ":qnx": ["-lregex"], + ":windows": [], + ":freebsd": [ + "-lm", + "-pthread", + ], + ":openbsd": [ + "-lm", + "-pthread", + ], + "//conditions:default": ["-pthread"], + }), + deps = select({ + ":has_absl": [ + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/debugging:failure_signal_handler", + "@com_google_absl//absl/debugging:stacktrace", + "@com_google_absl//absl/debugging:symbolize", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/flags:reflection", + "@com_google_absl//absl/flags:usage", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:any", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:variant", + "@com_googlesource_code_re2//:re2", + ], + "//conditions:default": [], + }), +) + +cc_library( + name = "gtest_main", + srcs = ["googlemock/src/gmock_main.cc"], + features = select({ + ":windows": ["windows_export_all_symbols"], + "//conditions:default": [], + }), + deps = [":gtest"], +) + +# The following rules build samples of how to use gTest. +cc_library( + name = "gtest_sample_lib", + srcs = [ + "googletest/samples/sample1.cc", + "googletest/samples/sample2.cc", + "googletest/samples/sample4.cc", + ], + hdrs = [ + "googletest/samples/prime_tables.h", + "googletest/samples/sample1.h", + "googletest/samples/sample2.h", + "googletest/samples/sample3-inl.h", + "googletest/samples/sample4.h", + ], + features = select({ + ":windows": ["windows_export_all_symbols"], + "//conditions:default": [], + }), +) + +cc_test( + name = "gtest_samples", + size = "small", + # All Samples except: + # sample9 (main) + # sample10 (main and takes a command line option and needs to be separate) + srcs = [ + "googletest/samples/sample1_unittest.cc", + "googletest/samples/sample2_unittest.cc", + "googletest/samples/sample3_unittest.cc", + "googletest/samples/sample4_unittest.cc", + "googletest/samples/sample5_unittest.cc", + "googletest/samples/sample6_unittest.cc", + "googletest/samples/sample7_unittest.cc", + "googletest/samples/sample8_unittest.cc", + ], + linkstatic = 0, + deps = [ + "gtest_sample_lib", + ":gtest_main", + ], +) + +cc_test( + name = "sample9_unittest", + size = "small", + srcs = ["googletest/samples/sample9_unittest.cc"], + deps = [":gtest"], +) + +cc_test( + name = "sample10_unittest", + size = "small", + srcs = ["googletest/samples/sample10_unittest.cc"], + deps = [":gtest"], +) diff --git a/Engine/lib/assimp/contrib/googletest/CMakeLists.txt b/Engine/lib/assimp/contrib/googletest/CMakeLists.txt new file mode 100644 index 000000000..089ac987f --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/CMakeLists.txt @@ -0,0 +1,27 @@ +# Note: CMake support is community-based. The maintainers do not use CMake +# internally. + +cmake_minimum_required(VERSION 3.13) + +project(googletest-distribution) +set(GOOGLETEST_VERSION 1.14.0) + +if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +enable_testing() + +include(CMakeDependentOption) +include(GNUInstallDirs) + +#Note that googlemock target already builds googletest +option(BUILD_GMOCK "Builds the googlemock subproject" ON) +option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON) +option(GTEST_HAS_ABSL "Use Abseil and RE2. Requires Abseil and RE2 to be separately added to the build." OFF) + +if(BUILD_GMOCK) + add_subdirectory( googlemock ) +else() + add_subdirectory( googletest ) +endif() diff --git a/Engine/lib/assimp/contrib/googletest/CONTRIBUTING.md b/Engine/lib/assimp/contrib/googletest/CONTRIBUTING.md new file mode 100644 index 000000000..8bed14b26 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/CONTRIBUTING.md @@ -0,0 +1,141 @@ +# How to become a contributor and submit your own code + +## Contributor License Agreements + +We'd love to accept your patches! Before we can take them, we have to jump a +couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement +(CLA). + +* If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an + [individual CLA](https://developers.google.com/open-source/cla/individual). +* If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a + [corporate CLA](https://developers.google.com/open-source/cla/corporate). + +Follow either of the two links above to access the appropriate CLA and +instructions for how to sign and return it. Once we receive it, we'll be able to +accept your pull requests. + +## Are you a Googler? + +If you are a Googler, please make an attempt to submit an internal contribution +rather than a GitHub Pull Request. If you are not able to submit internally, a +PR is acceptable as an alternative. + +## Contributing A Patch + +1. Submit an issue describing your proposed change to the + [issue tracker](https://github.com/google/googletest/issues). +2. Please don't mix more than one logical change per submittal, because it + makes the history hard to follow. If you want to make a change that doesn't + have a corresponding issue in the issue tracker, please create one. +3. Also, coordinate with team members that are listed on the issue in question. + This ensures that work isn't being duplicated and communicating your plan + early also generally leads to better patches. +4. If your proposed change is accepted, and you haven't already done so, sign a + Contributor License Agreement + ([see details above](#contributor-license-agreements)). +5. Fork the desired repo, develop and test your code changes. +6. Ensure that your code adheres to the existing style in the sample to which + you are contributing. +7. Ensure that your code has an appropriate set of unit tests which all pass. +8. Submit a pull request. + +## The Google Test and Google Mock Communities + +The Google Test community exists primarily through the +[discussion group](http://groups.google.com/group/googletestframework) and the +GitHub repository. Likewise, the Google Mock community exists primarily through +their own [discussion group](http://groups.google.com/group/googlemock). You are +definitely encouraged to contribute to the discussion and you can also help us +to keep the effectiveness of the group high by following and promoting the +guidelines listed here. + +### Please Be Friendly + +Showing courtesy and respect to others is a vital part of the Google culture, +and we strongly encourage everyone participating in Google Test development to +join us in accepting nothing less. Of course, being courteous is not the same as +failing to constructively disagree with each other, but it does mean that we +should be respectful of each other when enumerating the 42 technical reasons +that a particular proposal may not be the best choice. There's never a reason to +be antagonistic or dismissive toward anyone who is sincerely trying to +contribute to a discussion. + +Sure, C++ testing is serious business and all that, but it's also a lot of fun. +Let's keep it that way. Let's strive to be one of the friendliest communities in +all of open source. + +As always, discuss Google Test in the official GoogleTest discussion group. You +don't have to actually submit code in order to sign up. Your participation +itself is a valuable contribution. + +## Style + +To keep the source consistent, readable, diffable and easy to merge, we use a +fairly rigid coding style, as defined by the +[google-styleguide](https://github.com/google/styleguide) project. All patches +will be expected to conform to the style outlined +[here](https://google.github.io/styleguide/cppguide.html). Use +[.clang-format](https://github.com/google/googletest/blob/main/.clang-format) to +check your formatting. + +## Requirements for Contributors + +If you plan to contribute a patch, you need to build Google Test, Google Mock, +and their own tests from a git checkout, which has further requirements: + +* [Python](https://www.python.org/) v3.6 or newer (for running some of the + tests and re-generating certain source files from templates) +* [CMake](https://cmake.org/) v2.8.12 or newer + +## Developing Google Test and Google Mock + +This section discusses how to make your own changes to the Google Test project. + +### Testing Google Test and Google Mock Themselves + +To make sure your changes work as intended and don't break existing +functionality, you'll want to compile and run Google Test and GoogleMock's own +tests. For that you can use CMake: + +``` +mkdir mybuild +cd mybuild +cmake -Dgtest_build_tests=ON -Dgmock_build_tests=ON ${GTEST_REPO_DIR} +``` + +To choose between building only Google Test or Google Mock, you may modify your +cmake command to be one of each + +``` +cmake -Dgtest_build_tests=ON ${GTEST_DIR} # sets up Google Test tests +cmake -Dgmock_build_tests=ON ${GMOCK_DIR} # sets up Google Mock tests +``` + +Make sure you have Python installed, as some of Google Test's tests are written +in Python. If the cmake command complains about not being able to find Python +(`Could NOT find PythonInterp (missing: PYTHON_EXECUTABLE)`), try telling it +explicitly where your Python executable can be found: + +``` +cmake -DPYTHON_EXECUTABLE=path/to/python ... +``` + +Next, you can build Google Test and / or Google Mock and all desired tests. On +\*nix, this is usually done by + +``` +make +``` + +To run the tests, do + +``` +make test +``` + +All tests should pass. diff --git a/Engine/lib/assimp/contrib/gtest/CONTRIBUTORS b/Engine/lib/assimp/contrib/googletest/CONTRIBUTORS similarity index 60% rename from Engine/lib/assimp/contrib/gtest/CONTRIBUTORS rename to Engine/lib/assimp/contrib/googletest/CONTRIBUTORS index feae2fc04..77397a5b5 100644 --- a/Engine/lib/assimp/contrib/gtest/CONTRIBUTORS +++ b/Engine/lib/assimp/contrib/googletest/CONTRIBUTORS @@ -5,33 +5,61 @@ Ajay Joshi Balázs Dán +Benoit Sigoure Bharat Mediratta +Bogdan Piloca Chandler Carruth Chris Prince Chris Taylor Dan Egnor +Dave MacLachlan +David Anderson +Dean Sturtevant Eric Roman +Gene Volovich Hady Zalek +Hal Burch Jeffrey Yasskin +Jim Keller +Joe Walnes +Jon Wray Jói Sigurðsson Keir Mierle Keith Ray Kenton Varda +Kostya Serebryany +Krystian Kuzniarek +Lev Makhlis Manuel Klimek +Mario Tanev +Mark Paskin Markus Heule +Martijn Vels +Matthew Simmons Mika Raento +Mike Bland Miklós Fazekas +Neal Norwitz +Nermin Ozkiranartli +Owen Carlsen +Paneendra Ba Pasi Valminen Patrick Hanna Patrick Riley +Paul Menage Peter Kaminski +Piotr Kaminski Preston Jackson Rainer Klaffenboeck Russ Cox Russ Rufer Sean Mcafee Sigurður Ãsgeirsson +Sverre Sundsdal +Szymon Sobik +Takeshi Yoshino Tracy Bialik Vadim Berman Vlad Losev +Wolfgang Klier Zhanyong Wan diff --git a/Engine/lib/assimp/contrib/gtest/LICENSE b/Engine/lib/assimp/contrib/googletest/LICENSE similarity index 100% rename from Engine/lib/assimp/contrib/gtest/LICENSE rename to Engine/lib/assimp/contrib/googletest/LICENSE diff --git a/Engine/lib/assimp/contrib/googletest/README.md b/Engine/lib/assimp/contrib/googletest/README.md new file mode 100644 index 000000000..443e02069 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/README.md @@ -0,0 +1,146 @@ +# GoogleTest + +### Announcements + +#### Live at Head + +GoogleTest now follows the +[Abseil Live at Head philosophy](https://abseil.io/about/philosophy#upgrade-support). +We recommend +[updating to the latest commit in the `main` branch as often as possible](https://github.com/abseil/abseil-cpp/blob/master/FAQ.md#what-is-live-at-head-and-how-do-i-do-it). +We do publish occasional semantic versions, tagged with +`v${major}.${minor}.${patch}` (e.g. `v1.13.0`). + +#### Documentation Updates + +Our documentation is now live on GitHub Pages at +https://google.github.io/googletest/. We recommend browsing the documentation on +GitHub Pages rather than directly in the repository. + +#### Release 1.13.0 + +[Release 1.13.0](https://github.com/google/googletest/releases/tag/v1.13.0) is +now available. + +The 1.13.x branch requires at least C++14. + +#### Continuous Integration + +We use Google's internal systems for continuous integration. \ +GitHub Actions were added for the convenience of open-source contributors. They +are exclusively maintained by the open-source community and not used by the +GoogleTest team. + +#### Coming Soon + +* We are planning to take a dependency on + [Abseil](https://github.com/abseil/abseil-cpp). +* More documentation improvements are planned. + +## Welcome to **GoogleTest**, Google's C++ test framework! + +This repository is a merger of the formerly separate GoogleTest and GoogleMock +projects. These were so closely related that it makes sense to maintain and +release them together. + +### Getting Started + +See the [GoogleTest User's Guide](https://google.github.io/googletest/) for +documentation. We recommend starting with the +[GoogleTest Primer](https://google.github.io/googletest/primer.html). + +More information about building GoogleTest can be found at +[googletest/README.md](googletest/README.md). + +## Features + +* xUnit test framework: \ + Googletest is based on the [xUnit](https://en.wikipedia.org/wiki/XUnit) + testing framework, a popular architecture for unit testing +* Test discovery: \ + Googletest automatically discovers and runs your tests, eliminating the need + to manually register your tests +* Rich set of assertions: \ + Googletest provides a variety of assertions, such as equality, inequality, + exceptions, and more, making it easy to test your code +* User-defined assertions: \ + You can define your own assertions with Googletest, making it simple to + write tests that are specific to your code +* Death tests: \ + Googletest supports death tests, which verify that your code exits in a + certain way, making it useful for testing error-handling code +* Fatal and non-fatal failures: \ + You can specify whether a test failure should be treated as fatal or + non-fatal with Googletest, allowing tests to continue running even if a + failure occurs +* Value-parameterized tests: \ + Googletest supports value-parameterized tests, which run multiple times with + different input values, making it useful for testing functions that take + different inputs +* Type-parameterized tests: \ + Googletest also supports type-parameterized tests, which run with different + data types, making it useful for testing functions that work with different + data types +* Various options for running tests: \ + Googletest provides many options for running tests including running + individual tests, running tests in a specific order and running tests in + parallel + +## Supported Platforms + +GoogleTest follows Google's +[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support). +See +[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md) +for a list of currently supported versions of compilers, platforms, and build +tools. + +## Who Is Using GoogleTest? + +In addition to many internal projects at Google, GoogleTest is also used by the +following notable projects: + +* The [Chromium projects](http://www.chromium.org/) (behind the Chrome browser + and Chrome OS). +* The [LLVM](http://llvm.org/) compiler. +* [Protocol Buffers](https://github.com/google/protobuf), Google's data + interchange format. +* The [OpenCV](http://opencv.org/) computer vision library. + +## Related Open Source Projects + +[GTest Runner](https://github.com/nholthaus/gtest-runner) is a Qt5 based +automated test-runner and Graphical User Interface with powerful features for +Windows and Linux platforms. + +[GoogleTest UI](https://github.com/ospector/gtest-gbar) is a test runner that +runs your test binary, allows you to track its progress via a progress bar, and +displays a list of test failures. Clicking on one shows failure text. GoogleTest +UI is written in C#. + +[GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event +listener for GoogleTest that implements the +[TAP protocol](https://en.wikipedia.org/wiki/Test_Anything_Protocol) for test +result output. If your test runner understands TAP, you may find it useful. + +[gtest-parallel](https://github.com/google/gtest-parallel) is a test runner that +runs tests from your binary in parallel to provide significant speed-up. + +[GoogleTest Adapter](https://marketplace.visualstudio.com/items?itemName=DavidSchuldenfrei.gtest-adapter) +is a VS Code extension allowing to view GoogleTest in a tree view and run/debug +your tests. + +[C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) is a VS +Code extension allowing to view GoogleTest in a tree view and run/debug your +tests. + +[Cornichon](https://pypi.org/project/cornichon/) is a small Gherkin DSL parser +that generates stub code for GoogleTest. + +## Contributing Changes + +Please read +[`CONTRIBUTING.md`](https://github.com/google/googletest/blob/main/CONTRIBUTING.md) +for details on how to contribute to this project. + +Happy testing! diff --git a/Engine/lib/assimp/contrib/googletest/WORKSPACE b/Engine/lib/assimp/contrib/googletest/WORKSPACE new file mode 100644 index 000000000..f819ffe61 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/WORKSPACE @@ -0,0 +1,27 @@ +workspace(name = "com_google_googletest") + +load("//:googletest_deps.bzl", "googletest_deps") +googletest_deps() + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_python", # 2023-07-31T20:39:27Z + sha256 = "1250b59a33c591a1c4ba68c62e95fc88a84c334ec35a2e23f46cbc1b9a5a8b55", + strip_prefix = "rules_python-e355becc30275939d87116a4ec83dad4bb50d9e1", + urls = ["https://github.com/bazelbuild/rules_python/archive/e355becc30275939d87116a4ec83dad4bb50d9e1.zip"], +) + +http_archive( + name = "bazel_skylib", # 2023-05-31T19:24:07Z + sha256 = "08c0386f45821ce246bbbf77503c973246ed6ee5c3463e41efc197fa9bc3a7f4", + strip_prefix = "bazel-skylib-288731ef9f7f688932bd50e704a91a45ec185f9b", + urls = ["https://github.com/bazelbuild/bazel-skylib/archive/288731ef9f7f688932bd50e704a91a45ec185f9b.zip"], +) + +http_archive( + name = "platforms", # 2023-07-28T19:44:27Z + sha256 = "40eb313613ff00a5c03eed20aba58890046f4d38dec7344f00bb9a8867853526", + strip_prefix = "platforms-4ad40ef271da8176d4fc0194d2089b8a76e19d7b", + urls = ["https://github.com/bazelbuild/platforms/archive/4ad40ef271da8176d4fc0194d2089b8a76e19d7b.zip"], +) diff --git a/Engine/lib/assimp/contrib/googletest/ci/linux-presubmit.sh b/Engine/lib/assimp/contrib/googletest/ci/linux-presubmit.sh new file mode 100644 index 000000000..6bac88787 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/ci/linux-presubmit.sh @@ -0,0 +1,137 @@ +#!/bin/bash +# +# Copyright 2020, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set -euox pipefail + +readonly LINUX_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20230217" +readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20230120" + +if [[ -z ${GTEST_ROOT:-} ]]; then + GTEST_ROOT="$(realpath $(dirname ${0})/..)" +fi + +if [[ -z ${STD:-} ]]; then + STD="c++14 c++17 c++20" +fi + +# Test the CMake build +for cc in /usr/local/bin/gcc /opt/llvm/clang/bin/clang; do + for cmake_off_on in OFF ON; do + time docker run \ + --volume="${GTEST_ROOT}:/src:ro" \ + --tmpfs="/build:exec" \ + --workdir="/build" \ + --rm \ + --env="CC=${cc}" \ + --env=CXXFLAGS="-Werror -Wdeprecated" \ + ${LINUX_LATEST_CONTAINER} \ + /bin/bash -c " + cmake /src \ + -DCMAKE_CXX_STANDARD=14 \ + -Dgtest_build_samples=ON \ + -Dgtest_build_tests=ON \ + -Dgmock_build_tests=ON \ + -Dcxx_no_exception=${cmake_off_on} \ + -Dcxx_no_rtti=${cmake_off_on} && \ + make -j$(nproc) && \ + ctest -j$(nproc) --output-on-failure" + done +done + +# Do one test with an older version of GCC +time docker run \ + --volume="${GTEST_ROOT}:/src:ro" \ + --workdir="/src" \ + --rm \ + --env="CC=/usr/local/bin/gcc" \ + --env="BAZEL_CXXOPTS=-std=c++14" \ + ${LINUX_GCC_FLOOR_CONTAINER} \ + /usr/local/bin/bazel test ... \ + --copt="-Wall" \ + --copt="-Werror" \ + --copt="-Wuninitialized" \ + --copt="-Wundef" \ + --copt="-Wno-error=pragmas" \ + --distdir="/bazel-distdir" \ + --features=external_include_paths \ + --keep_going \ + --show_timestamps \ + --test_output=errors + +# Test GCC +for std in ${STD}; do + for absl in 0 1; do + time docker run \ + --volume="${GTEST_ROOT}:/src:ro" \ + --workdir="/src" \ + --rm \ + --env="CC=/usr/local/bin/gcc" \ + --env="BAZEL_CXXOPTS=-std=${std}" \ + ${LINUX_LATEST_CONTAINER} \ + /usr/local/bin/bazel test ... \ + --copt="-Wall" \ + --copt="-Werror" \ + --copt="-Wuninitialized" \ + --copt="-Wundef" \ + --define="absl=${absl}" \ + --distdir="/bazel-distdir" \ + --features=external_include_paths \ + --keep_going \ + --show_timestamps \ + --test_output=errors + done +done + +# Test Clang +for std in ${STD}; do + for absl in 0 1; do + time docker run \ + --volume="${GTEST_ROOT}:/src:ro" \ + --workdir="/src" \ + --rm \ + --env="CC=/opt/llvm/clang/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}" \ + ${LINUX_LATEST_CONTAINER} \ + /usr/local/bin/bazel test ... \ + --copt="--gcc-toolchain=/usr/local" \ + --copt="-Wall" \ + --copt="-Werror" \ + --copt="-Wuninitialized" \ + --copt="-Wundef" \ + --define="absl=${absl}" \ + --distdir="/bazel-distdir" \ + --features=external_include_paths \ + --keep_going \ + --linkopt="--gcc-toolchain=/usr/local" \ + --show_timestamps \ + --test_output=errors + done +done diff --git a/Engine/lib/assimp/contrib/googletest/ci/macos-presubmit.sh b/Engine/lib/assimp/contrib/googletest/ci/macos-presubmit.sh new file mode 100644 index 000000000..681ebc2a9 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/ci/macos-presubmit.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# +# Copyright 2020, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set -euox pipefail + +if [[ -z ${GTEST_ROOT:-} ]]; then + GTEST_ROOT="$(realpath $(dirname ${0})/..)" +fi + +# Test the CMake build +for cmake_off_on in OFF ON; do + BUILD_DIR=$(mktemp -d build_dir.XXXXXXXX) + cd ${BUILD_DIR} + time cmake ${GTEST_ROOT} \ + -DCMAKE_CXX_STANDARD=14 \ + -Dgtest_build_samples=ON \ + -Dgtest_build_tests=ON \ + -Dgmock_build_tests=ON \ + -Dcxx_no_exception=${cmake_off_on} \ + -Dcxx_no_rtti=${cmake_off_on} + time make + time ctest -j$(nproc) --output-on-failure +done + +# Test the Bazel build + +# If we are running on Kokoro, check for a versioned Bazel binary. +KOKORO_GFILE_BAZEL_BIN="bazel-5.1.1-darwin-x86_64" +if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then + BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}" + chmod +x ${BAZEL_BIN} +else + BAZEL_BIN="bazel" +fi + +cd ${GTEST_ROOT} +for absl in 0 1; do + ${BAZEL_BIN} test ... \ + --copt="-Wall" \ + --copt="-Werror" \ + --copt="-Wundef" \ + --cxxopt="-std=c++14" \ + --define="absl=${absl}" \ + --features=external_include_paths \ + --keep_going \ + --show_timestamps \ + --test_output=errors +done diff --git a/Engine/lib/assimp/contrib/googletest/ci/windows-presubmit.bat b/Engine/lib/assimp/contrib/googletest/ci/windows-presubmit.bat new file mode 100644 index 000000000..48962eb9e --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/ci/windows-presubmit.bat @@ -0,0 +1,58 @@ +SETLOCAL ENABLEDELAYEDEXPANSION + +SET BAZEL_EXE=%KOKORO_GFILE_DIR%\bazel-5.1.1-windows-x86_64.exe + +SET PATH=C:\Python34;%PATH% +SET BAZEL_PYTHON=C:\python34\python.exe +SET BAZEL_SH=C:\tools\msys64\usr\bin\bash.exe +SET CMAKE_BIN="cmake.exe" +SET CTEST_BIN="ctest.exe" +SET CTEST_OUTPUT_ON_FAILURE=1 +SET CMAKE_BUILD_PARALLEL_LEVEL=16 +SET CTEST_PARALLEL_LEVEL=16 + +IF EXIST git\googletest ( + CD git\googletest +) ELSE IF EXIST github\googletest ( + CD github\googletest +) + +IF %errorlevel% neq 0 EXIT /B 1 + +:: ---------------------------------------------------------------------------- +:: CMake +MKDIR cmake_msvc2022 +CD cmake_msvc2022 + +%CMAKE_BIN% .. ^ + -G "Visual Studio 17 2022" ^ + -DPYTHON_EXECUTABLE:FILEPATH=c:\python37\python.exe ^ + -DPYTHON_INCLUDE_DIR:PATH=c:\python37\include ^ + -DPYTHON_LIBRARY:FILEPATH=c:\python37\lib\site-packages\pip ^ + -Dgtest_build_samples=ON ^ + -Dgtest_build_tests=ON ^ + -Dgmock_build_tests=ON +IF %errorlevel% neq 0 EXIT /B 1 + +%CMAKE_BIN% --build . --target ALL_BUILD --config Debug -- -maxcpucount +IF %errorlevel% neq 0 EXIT /B 1 + +%CTEST_BIN% -C Debug --timeout 600 +IF %errorlevel% neq 0 EXIT /B 1 + +CD .. +RMDIR /S /Q cmake_msvc2022 + +:: ---------------------------------------------------------------------------- +:: Bazel + +SET BAZEL_VS=C:\Program Files\Microsoft Visual Studio\2022\Community +%BAZEL_EXE% test ... ^ + --compilation_mode=dbg ^ + --copt=/std:c++14 ^ + --copt=/WX ^ + --features=external_include_paths ^ + --keep_going ^ + --test_output=errors ^ + --test_tag_filters=-no_test_msvc2017 +IF %errorlevel% neq 0 EXIT /B 1 diff --git a/Engine/lib/assimp/contrib/googletest/docs/_config.yml b/Engine/lib/assimp/contrib/googletest/docs/_config.yml new file mode 100644 index 000000000..d12867eab --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/_config.yml @@ -0,0 +1 @@ +title: GoogleTest diff --git a/Engine/lib/assimp/contrib/googletest/docs/_data/navigation.yml b/Engine/lib/assimp/contrib/googletest/docs/_data/navigation.yml new file mode 100644 index 000000000..9f3332708 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/_data/navigation.yml @@ -0,0 +1,43 @@ +nav: +- section: "Get Started" + items: + - title: "Supported Platforms" + url: "/platforms.html" + - title: "Quickstart: Bazel" + url: "/quickstart-bazel.html" + - title: "Quickstart: CMake" + url: "/quickstart-cmake.html" +- section: "Guides" + items: + - title: "GoogleTest Primer" + url: "/primer.html" + - title: "Advanced Topics" + url: "/advanced.html" + - title: "Mocking for Dummies" + url: "/gmock_for_dummies.html" + - title: "Mocking Cookbook" + url: "/gmock_cook_book.html" + - title: "Mocking Cheat Sheet" + url: "/gmock_cheat_sheet.html" +- section: "References" + items: + - title: "Testing Reference" + url: "/reference/testing.html" + - title: "Mocking Reference" + url: "/reference/mocking.html" + - title: "Assertions" + url: "/reference/assertions.html" + - title: "Matchers" + url: "/reference/matchers.html" + - title: "Actions" + url: "/reference/actions.html" + - title: "Testing FAQ" + url: "/faq.html" + - title: "Mocking FAQ" + url: "/gmock_faq.html" + - title: "Code Samples" + url: "/samples.html" + - title: "Using pkg-config" + url: "/pkgconfig.html" + - title: "Community Documentation" + url: "/community_created_documentation.html" diff --git a/Engine/lib/assimp/contrib/googletest/docs/_layouts/default.html b/Engine/lib/assimp/contrib/googletest/docs/_layouts/default.html new file mode 100644 index 000000000..c7f331b87 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/_layouts/default.html @@ -0,0 +1,58 @@ + + + + + + + +{% seo %} + + + + + + +
+
+ {{ content }} +
+ +
+ + + + diff --git a/Engine/lib/assimp/contrib/googletest/docs/_sass/main.scss b/Engine/lib/assimp/contrib/googletest/docs/_sass/main.scss new file mode 100644 index 000000000..92edc877a --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/_sass/main.scss @@ -0,0 +1,200 @@ +// Styles for GoogleTest docs website on GitHub Pages. +// Color variables are defined in +// https://github.com/pages-themes/primer/tree/master/_sass/primer-support/lib/variables + +$sidebar-width: 260px; + +body { + display: flex; + margin: 0; +} + +.sidebar { + background: $black; + color: $text-white; + flex-shrink: 0; + height: 100vh; + overflow: auto; + position: sticky; + top: 0; + width: $sidebar-width; +} + +.sidebar h1 { + font-size: 1.5em; +} + +.sidebar h2 { + color: $gray-light; + font-size: 0.8em; + font-weight: normal; + margin-bottom: 0.8em; + padding-left: 2.5em; + text-transform: uppercase; +} + +.sidebar .header { + background: $black; + padding: 2em; + position: sticky; + top: 0; + width: 100%; +} + +.sidebar .header a { + color: $text-white; + text-decoration: none; +} + +.sidebar .nav-toggle { + display: none; +} + +.sidebar .expander { + cursor: pointer; + display: none; + height: 3em; + position: absolute; + right: 1em; + top: 1.5em; + width: 3em; +} + +.sidebar .expander .arrow { + border: solid $white; + border-width: 0 3px 3px 0; + display: block; + height: 0.7em; + margin: 1em auto; + transform: rotate(45deg); + transition: transform 0.5s; + width: 0.7em; +} + +.sidebar nav { + width: 100%; +} + +.sidebar nav ul { + list-style-type: none; + margin-bottom: 1em; + padding: 0; + + &:last-child { + margin-bottom: 2em; + } + + a { + text-decoration: none; + } + + li { + color: $text-white; + padding-left: 2em; + text-decoration: none; + } + + li.active { + background: $border-gray-darker; + font-weight: bold; + } + + li:hover { + background: $border-gray-darker; + } +} + +.main { + background-color: $bg-gray; + width: calc(100% - #{$sidebar-width}); +} + +.main .main-inner { + background-color: $white; + padding: 2em; +} + +.main .footer { + margin: 0; + padding: 2em; +} + +.main table th { + text-align: left; +} + +.main .callout { + border-left: 0.25em solid $white; + padding: 1em; + + a { + text-decoration: underline; + } + + &.important { + background-color: $bg-yellow-light; + border-color: $bg-yellow; + color: $black; + } + + &.note { + background-color: $bg-blue-light; + border-color: $text-blue; + color: $text-blue; + } + + &.tip { + background-color: $green-000; + border-color: $green-700; + color: $green-700; + } + + &.warning { + background-color: $red-000; + border-color: $text-red; + color: $text-red; + } +} + +.main .good pre { + background-color: $bg-green-light; +} + +.main .bad pre { + background-color: $red-000; +} + +@media all and (max-width: 768px) { + body { + flex-direction: column; + } + + .sidebar { + height: auto; + position: relative; + width: 100%; + } + + .sidebar .expander { + display: block; + } + + .sidebar nav { + height: 0; + overflow: hidden; + } + + .sidebar .nav-toggle:checked { + & ~ nav { + height: auto; + } + + & + .expander .arrow { + transform: rotate(-135deg); + } + } + + .main { + width: 100%; + } +} diff --git a/Engine/lib/assimp/contrib/googletest/docs/advanced.md b/Engine/lib/assimp/contrib/googletest/docs/advanced.md new file mode 100644 index 000000000..3871db13b --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/advanced.md @@ -0,0 +1,2436 @@ +# Advanced GoogleTest Topics + +## Introduction + +Now that you have read the [GoogleTest Primer](primer.md) and learned how to +write tests using GoogleTest, it's time to learn some new tricks. This document +will show you more assertions as well as how to construct complex failure +messages, propagate fatal failures, reuse and speed up your test fixtures, and +use various flags with your tests. + +## More Assertions + +This section covers some less frequently used, but still significant, +assertions. + +### Explicit Success and Failure + +See [Explicit Success and Failure](reference/assertions.md#success-failure) in +the Assertions Reference. + +### Exception Assertions + +See [Exception Assertions](reference/assertions.md#exceptions) in the Assertions +Reference. + +### Predicate Assertions for Better Error Messages + +Even though GoogleTest has a rich set of assertions, they can never be complete, +as it's impossible (nor a good idea) to anticipate all scenarios a user might +run into. Therefore, sometimes a user has to use `EXPECT_TRUE()` to check a +complex expression, for lack of a better macro. This has the problem of not +showing you the values of the parts of the expression, making it hard to +understand what went wrong. As a workaround, some users choose to construct the +failure message by themselves, streaming it into `EXPECT_TRUE()`. However, this +is awkward especially when the expression has side-effects or is expensive to +evaluate. + +GoogleTest gives you three different options to solve this problem: + +#### Using an Existing Boolean Function + +If you already have a function or functor that returns `bool` (or a type that +can be implicitly converted to `bool`), you can use it in a *predicate +assertion* to get the function arguments printed for free. See +[`EXPECT_PRED*`](reference/assertions.md#EXPECT_PRED) in the Assertions +Reference for details. + +#### Using a Function That Returns an AssertionResult + +While `EXPECT_PRED*()` and friends are handy for a quick job, the syntax is not +satisfactory: you have to use different macros for different arities, and it +feels more like Lisp than C++. The `::testing::AssertionResult` class solves +this problem. + +An `AssertionResult` object represents the result of an assertion (whether it's +a success or a failure, and an associated message). You can create an +`AssertionResult` using one of these factory functions: + +```c++ +namespace testing { + +// Returns an AssertionResult object to indicate that an assertion has +// succeeded. +AssertionResult AssertionSuccess(); + +// Returns an AssertionResult object to indicate that an assertion has +// failed. +AssertionResult AssertionFailure(); + +} +``` + +You can then use the `<<` operator to stream messages to the `AssertionResult` +object. + +To provide more readable messages in Boolean assertions (e.g. `EXPECT_TRUE()`), +write a predicate function that returns `AssertionResult` instead of `bool`. For +example, if you define `IsEven()` as: + +```c++ +testing::AssertionResult IsEven(int n) { + if ((n % 2) == 0) + return testing::AssertionSuccess(); + else + return testing::AssertionFailure() << n << " is odd"; +} +``` + +instead of: + +```c++ +bool IsEven(int n) { + return (n % 2) == 0; +} +``` + +the failed assertion `EXPECT_TRUE(IsEven(Fib(4)))` will print: + +```none +Value of: IsEven(Fib(4)) + Actual: false (3 is odd) +Expected: true +``` + +instead of a more opaque + +```none +Value of: IsEven(Fib(4)) + Actual: false +Expected: true +``` + +If you want informative messages in `EXPECT_FALSE` and `ASSERT_FALSE` as well +(one third of Boolean assertions in the Google code base are negative ones), and +are fine with making the predicate slower in the success case, you can supply a +success message: + +```c++ +testing::AssertionResult IsEven(int n) { + if ((n % 2) == 0) + return testing::AssertionSuccess() << n << " is even"; + else + return testing::AssertionFailure() << n << " is odd"; +} +``` + +Then the statement `EXPECT_FALSE(IsEven(Fib(6)))` will print + +```none + Value of: IsEven(Fib(6)) + Actual: true (8 is even) + Expected: false +``` + +#### Using a Predicate-Formatter + +If you find the default message generated by +[`EXPECT_PRED*`](reference/assertions.md#EXPECT_PRED) and +[`EXPECT_TRUE`](reference/assertions.md#EXPECT_TRUE) unsatisfactory, or some +arguments to your predicate do not support streaming to `ostream`, you can +instead use *predicate-formatter assertions* to *fully* customize how the +message is formatted. See +[`EXPECT_PRED_FORMAT*`](reference/assertions.md#EXPECT_PRED_FORMAT) in the +Assertions Reference for details. + +### Floating-Point Comparison + +See [Floating-Point Comparison](reference/assertions.md#floating-point) in the +Assertions Reference. + +#### Floating-Point Predicate-Format Functions + +Some floating-point operations are useful, but not that often used. In order to +avoid an explosion of new macros, we provide them as predicate-format functions +that can be used in the predicate assertion macro +[`EXPECT_PRED_FORMAT2`](reference/assertions.md#EXPECT_PRED_FORMAT), for +example: + +```c++ +using ::testing::FloatLE; +using ::testing::DoubleLE; +... +EXPECT_PRED_FORMAT2(FloatLE, val1, val2); +EXPECT_PRED_FORMAT2(DoubleLE, val1, val2); +``` + +The above code verifies that `val1` is less than, or approximately equal to, +`val2`. + +### Asserting Using gMock Matchers + +See [`EXPECT_THAT`](reference/assertions.md#EXPECT_THAT) in the Assertions +Reference. + +### More String Assertions + +(Please read the [previous](#asserting-using-gmock-matchers) section first if +you haven't.) + +You can use the gMock [string matchers](reference/matchers.md#string-matchers) +with [`EXPECT_THAT`](reference/assertions.md#EXPECT_THAT) to do more string +comparison tricks (sub-string, prefix, suffix, regular expression, and etc). For +example, + +```c++ +using ::testing::HasSubstr; +using ::testing::MatchesRegex; +... + ASSERT_THAT(foo_string, HasSubstr("needle")); + EXPECT_THAT(bar_string, MatchesRegex("\\w*\\d+")); +``` + +### Windows HRESULT assertions + +See [Windows HRESULT Assertions](reference/assertions.md#HRESULT) in the +Assertions Reference. + +### Type Assertions + +You can call the function + +```c++ +::testing::StaticAssertTypeEq(); +``` + +to assert that types `T1` and `T2` are the same. The function does nothing if +the assertion is satisfied. If the types are different, the function call will +fail to compile, the compiler error message will say that `T1 and T2 are not the +same type` and most likely (depending on the compiler) show you the actual +values of `T1` and `T2`. This is mainly useful inside template code. + +**Caveat**: When used inside a member function of a class template or a function +template, `StaticAssertTypeEq()` is effective only if the function is +instantiated. For example, given: + +```c++ +template class Foo { + public: + void Bar() { testing::StaticAssertTypeEq(); } +}; +``` + +the code: + +```c++ +void Test1() { Foo foo; } +``` + +will not generate a compiler error, as `Foo::Bar()` is never actually +instantiated. Instead, you need: + +```c++ +void Test2() { Foo foo; foo.Bar(); } +``` + +to cause a compiler error. + +### Assertion Placement + +You can use assertions in any C++ function. In particular, it doesn't have to be +a method of the test fixture class. The one constraint is that assertions that +generate a fatal failure (`FAIL*` and `ASSERT_*`) can only be used in +void-returning functions. This is a consequence of Google's not using +exceptions. By placing it in a non-void function you'll get a confusing compile +error like `"error: void value not ignored as it ought to be"` or `"cannot +initialize return object of type 'bool' with an rvalue of type 'void'"` or +`"error: no viable conversion from 'void' to 'string'"`. + +If you need to use fatal assertions in a function that returns non-void, one +option is to make the function return the value in an out parameter instead. For +example, you can rewrite `T2 Foo(T1 x)` to `void Foo(T1 x, T2* result)`. You +need to make sure that `*result` contains some sensible value even when the +function returns prematurely. As the function now returns `void`, you can use +any assertion inside of it. + +If changing the function's type is not an option, you should just use assertions +that generate non-fatal failures, such as `ADD_FAILURE*` and `EXPECT_*`. + +{: .callout .note} +NOTE: Constructors and destructors are not considered void-returning functions, +according to the C++ language specification, and so you may not use fatal +assertions in them; you'll get a compilation error if you try. Instead, either +call `abort` and crash the entire test executable, or put the fatal assertion in +a `SetUp`/`TearDown` function; see +[constructor/destructor vs. `SetUp`/`TearDown`](faq.md#CtorVsSetUp) + +{: .callout .warning} +WARNING: A fatal assertion in a helper function (private void-returning method) +called from a constructor or destructor does not terminate the current test, as +your intuition might suggest: it merely returns from the constructor or +destructor early, possibly leaving your object in a partially-constructed or +partially-destructed state! You almost certainly want to `abort` or use +`SetUp`/`TearDown` instead. + +## Skipping test execution + +Related to the assertions `SUCCEED()` and `FAIL()`, you can prevent further test +execution at runtime with the `GTEST_SKIP()` macro. This is useful when you need +to check for preconditions of the system under test during runtime and skip +tests in a meaningful way. + +`GTEST_SKIP()` can be used in individual test cases or in the `SetUp()` methods +of classes derived from either `::testing::Environment` or `::testing::Test`. +For example: + +```c++ +TEST(SkipTest, DoesSkip) { + GTEST_SKIP() << "Skipping single test"; + EXPECT_EQ(0, 1); // Won't fail; it won't be executed +} + +class SkipFixture : public ::testing::Test { + protected: + void SetUp() override { + GTEST_SKIP() << "Skipping all tests for this fixture"; + } +}; + +// Tests for SkipFixture won't be executed. +TEST_F(SkipFixture, SkipsOneTest) { + EXPECT_EQ(5, 7); // Won't fail +} +``` + +As with assertion macros, you can stream a custom message into `GTEST_SKIP()`. + +## Teaching GoogleTest How to Print Your Values + +When a test assertion such as `EXPECT_EQ` fails, GoogleTest prints the argument +values to help you debug. It does this using a user-extensible value printer. + +This printer knows how to print built-in C++ types, native arrays, STL +containers, and any type that supports the `<<` operator. For other types, it +prints the raw bytes in the value and hopes that you the user can figure it out. + +As mentioned earlier, the printer is *extensible*. That means you can teach it +to do a better job at printing your particular type than to dump the bytes. To +do that, define an `AbslStringify()` overload as a `friend` function template +for your type: + +```cpp +namespace foo { + +class Point { // We want GoogleTest to be able to print instances of this. + ... + // Provide a friend overload. + template + friend void AbslStringify(Sink& sink, const Point& point) { + absl::Format(&sink, "(%d, %d)", point.x, point.y); + } + + int x; + int y; +}; + +// If you can't declare the function in the class it's important that the +// AbslStringify overload is defined in the SAME namespace that defines Point. +// C++'s look-up rules rely on that. +enum class EnumWithStringify { kMany = 0, kChoices = 1 }; + +template +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::kMany ? "Many" : "Choices"); +} + +} // namespace foo +``` + +{: .callout .note} +Note: `AbslStringify()` utilizes a generic "sink" buffer to construct its +string. For more information about supported operations on `AbslStringify()`'s +sink, see go/abslstringify. + +`AbslStringify()` can also use `absl::StrFormat`'s catch-all `%v` type specifier +within its own format strings to perform type deduction. `Point` above could be +formatted as `"(%v, %v)"` for example, and deduce the `int` values as `%d`. + +Sometimes, `AbslStringify()` might not be an option: your team may wish to print +types with extra debugging information for testing purposes only. If so, you can +instead define a `PrintTo()` function like this: + +```c++ +#include + +namespace foo { + +class Point { + ... + friend void PrintTo(const Point& point, std::ostream* os) { + *os << "(" << point.x << "," << point.y << ")"; + } + + int x; + int y; +}; + +// If you can't declare the function in the class it's important that PrintTo() +// is defined in the SAME namespace that defines Point. C++'s look-up rules +// rely on that. +void PrintTo(const Point& point, std::ostream* os) { + *os << "(" << point.x << "," << point.y << ")"; +} + +} // namespace foo +``` + +If you have defined both `AbslStringify()` and `PrintTo()`, the latter will be +used by GoogleTest. This allows you to customize how the value appears in +GoogleTest's output without affecting code that relies on the behavior of +`AbslStringify()`. + +If you have an existing `<<` operator and would like to define an +`AbslStringify()`, the latter will be used for GoogleTest printing. + +If you want to print a value `x` using GoogleTest's value printer yourself, just +call `::testing::PrintToString(x)`, which returns an `std::string`: + +```c++ +vector > point_ints = GetPointIntVector(); + +EXPECT_TRUE(IsCorrectPointIntVector(point_ints)) + << "point_ints = " << testing::PrintToString(point_ints); +``` + +For more details regarding `AbslStringify()` and its integration with other +libraries, see go/abslstringify. + +## Death Tests + +In many applications, there are assertions that can cause application failure if +a condition is not met. These consistency checks, which ensure that the program +is in a known good state, are there to fail at the earliest possible time after +some program state is corrupted. If the assertion checks the wrong condition, +then the program may proceed in an erroneous state, which could lead to memory +corruption, security holes, or worse. Hence it is vitally important to test that +such assertion statements work as expected. + +Since these precondition checks cause the processes to die, we call such tests +_death tests_. More generally, any test that checks that a program terminates +(except by throwing an exception) in an expected fashion is also a death test. + +Note that if a piece of code throws an exception, we don't consider it "death" +for the purpose of death tests, as the caller of the code could catch the +exception and avoid the crash. If you want to verify exceptions thrown by your +code, see [Exception Assertions](#ExceptionAssertions). + +If you want to test `EXPECT_*()/ASSERT_*()` failures in your test code, see +["Catching" Failures](#catching-failures). + +### How to Write a Death Test + +GoogleTest provides assertion macros to support death tests. See +[Death Assertions](reference/assertions.md#death) in the Assertions Reference +for details. + +To write a death test, simply use one of the macros inside your test function. +For example, + +```c++ +TEST(MyDeathTest, Foo) { + // This death test uses a compound statement. + ASSERT_DEATH({ + int n = 5; + Foo(&n); + }, "Error on line .* of Foo()"); +} + +TEST(MyDeathTest, NormalExit) { + EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success"); +} + +TEST(MyDeathTest, KillProcess) { + EXPECT_EXIT(KillProcess(), testing::KilledBySignal(SIGKILL), + "Sending myself unblockable signal"); +} +``` + +verifies that: + +* calling `Foo(5)` causes the process to die with the given error message, +* calling `NormalExit()` causes the process to print `"Success"` to stderr and + exit with exit code 0, and +* calling `KillProcess()` kills the process with signal `SIGKILL`. + +The test function body may contain other assertions and statements as well, if +necessary. + +Note that a death test only cares about three things: + +1. does `statement` abort or exit the process? +2. (in the case of `ASSERT_EXIT` and `EXPECT_EXIT`) does the exit status + satisfy `predicate`? Or (in the case of `ASSERT_DEATH` and `EXPECT_DEATH`) + is the exit status non-zero? And +3. does the stderr output match `matcher`? + +In particular, if `statement` generates an `ASSERT_*` or `EXPECT_*` failure, it +will **not** cause the death test to fail, as GoogleTest assertions don't abort +the process. + +### Death Test Naming + +{: .callout .important} +IMPORTANT: We strongly recommend you to follow the convention of naming your +**test suite** (not test) `*DeathTest` when it contains a death test, as +demonstrated in the above example. The +[Death Tests And Threads](#death-tests-and-threads) section below explains why. + +If a test fixture class is shared by normal tests and death tests, you can use +`using` or `typedef` to introduce an alias for the fixture class and avoid +duplicating its code: + +```c++ +class FooTest : public testing::Test { ... }; + +using FooDeathTest = FooTest; + +TEST_F(FooTest, DoesThis) { + // normal test +} + +TEST_F(FooDeathTest, DoesThat) { + // death test +} +``` + +### Regular Expression Syntax + +When built with Bazel and using Abseil, GoogleTest uses the +[RE2](https://github.com/google/re2/wiki/Syntax) syntax. Otherwise, for POSIX +systems (Linux, Cygwin, Mac), GoogleTest uses the +[POSIX extended regular expression](http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html#tag_09_04) +syntax. To learn about POSIX syntax, you may want to read this +[Wikipedia entry](http://en.wikipedia.org/wiki/Regular_expression#POSIX_extended). + +On Windows, GoogleTest uses its own simple regular expression implementation. It +lacks many features. For example, we don't support union (`"x|y"`), grouping +(`"(xy)"`), brackets (`"[xy]"`), and repetition count (`"x{5,7}"`), among +others. Below is what we do support (`A` denotes a literal character, period +(`.`), or a single `\\ ` escape sequence; `x` and `y` denote regular +expressions.): + +Expression | Meaning +---------- | -------------------------------------------------------------- +`c` | matches any literal character `c` +`\\d` | matches any decimal digit +`\\D` | matches any character that's not a decimal digit +`\\f` | matches `\f` +`\\n` | matches `\n` +`\\r` | matches `\r` +`\\s` | matches any ASCII whitespace, including `\n` +`\\S` | matches any character that's not a whitespace +`\\t` | matches `\t` +`\\v` | matches `\v` +`\\w` | matches any letter, `_`, or decimal digit +`\\W` | matches any character that `\\w` doesn't match +`\\c` | matches any literal character `c`, which must be a punctuation +`.` | matches any single character except `\n` +`A?` | matches 0 or 1 occurrences of `A` +`A*` | matches 0 or many occurrences of `A` +`A+` | matches 1 or many occurrences of `A` +`^` | matches the beginning of a string (not that of each line) +`$` | matches the end of a string (not that of each line) +`xy` | matches `x` followed by `y` + +To help you determine which capability is available on your system, GoogleTest +defines macros to govern which regular expression it is using. The macros are: +`GTEST_USES_SIMPLE_RE=1` or `GTEST_USES_POSIX_RE=1`. If you want your death +tests to work in all cases, you can either `#if` on these macros or use the more +limited syntax only. + +### How It Works + +See [Death Assertions](reference/assertions.md#death) in the Assertions +Reference. + +### Death Tests And Threads + +The reason for the two death test styles has to do with thread safety. Due to +well-known problems with forking in the presence of threads, death tests should +be run in a single-threaded context. Sometimes, however, it isn't feasible to +arrange that kind of environment. For example, statically-initialized modules +may start threads before main is ever reached. Once threads have been created, +it may be difficult or impossible to clean them up. + +GoogleTest has three features intended to raise awareness of threading issues. + +1. A warning is emitted if multiple threads are running when a death test is + encountered. +2. Test suites with a name ending in "DeathTest" are run before all other + tests. +3. It uses `clone()` instead of `fork()` to spawn the child process on Linux + (`clone()` is not available on Cygwin and Mac), as `fork()` is more likely + to cause the child to hang when the parent process has multiple threads. + +It's perfectly fine to create threads inside a death test statement; they are +executed in a separate process and cannot affect the parent. + +### Death Test Styles + +The "threadsafe" death test style was introduced in order to help mitigate the +risks of testing in a possibly multithreaded environment. It trades increased +test execution time (potentially dramatically so) for improved thread safety. + +The automated testing framework does not set the style flag. You can choose a +particular style of death tests by setting the flag programmatically: + +```c++ +GTEST_FLAG_SET(death_test_style, "threadsafe"); +``` + +You can do this in `main()` to set the style for all death tests in the binary, +or in individual tests. Recall that flags are saved before running each test and +restored afterwards, so you need not do that yourself. For example: + +```c++ +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + GTEST_FLAG_SET(death_test_style, "fast"); + return RUN_ALL_TESTS(); +} + +TEST(MyDeathTest, TestOne) { + GTEST_FLAG_SET(death_test_style, "threadsafe"); + // This test is run in the "threadsafe" style: + ASSERT_DEATH(ThisShouldDie(), ""); +} + +TEST(MyDeathTest, TestTwo) { + // This test is run in the "fast" style: + ASSERT_DEATH(ThisShouldDie(), ""); +} +``` + +### Caveats + +The `statement` argument of `ASSERT_EXIT()` can be any valid C++ statement. If +it leaves the current function via a `return` statement or by throwing an +exception, the death test is considered to have failed. Some GoogleTest macros +may return from the current function (e.g. `ASSERT_TRUE()`), so be sure to avoid +them in `statement`. + +Since `statement` runs in the child process, any in-memory side effect (e.g. +modifying a variable, releasing memory, etc) it causes will *not* be observable +in the parent process. In particular, if you release memory in a death test, +your program will fail the heap check as the parent process will never see the +memory reclaimed. To solve this problem, you can + +1. try not to free memory in a death test; +2. free the memory again in the parent process; or +3. do not use the heap checker in your program. + +Due to an implementation detail, you cannot place multiple death test assertions +on the same line; otherwise, compilation will fail with an unobvious error +message. + +Despite the improved thread safety afforded by the "threadsafe" style of death +test, thread problems such as deadlock are still possible in the presence of +handlers registered with `pthread_atfork(3)`. + +## Using Assertions in Sub-routines + +{: .callout .note} +Note: If you want to put a series of test assertions in a subroutine to check +for a complex condition, consider using +[a custom GMock matcher](gmock_cook_book.md#NewMatchers) instead. This lets you +provide a more readable error message in case of failure and avoid all of the +issues described below. + +### Adding Traces to Assertions + +If a test sub-routine is called from several places, when an assertion inside it +fails, it can be hard to tell which invocation of the sub-routine the failure is +from. You can alleviate this problem using extra logging or custom failure +messages, but that usually clutters up your tests. A better solution is to use +the `SCOPED_TRACE` macro or the `ScopedTrace` utility: + +```c++ +SCOPED_TRACE(message); +``` + +```c++ +ScopedTrace trace("file_path", line_number, message); +``` + +where `message` can be anything streamable to `std::ostream`. `SCOPED_TRACE` +macro will cause the current file name, line number, and the given message to be +added in every failure message. `ScopedTrace` accepts explicit file name and +line number in arguments, which is useful for writing test helpers. The effect +will be undone when the control leaves the current lexical scope. + +For example, + +```c++ +10: void Sub1(int n) { +11: EXPECT_EQ(Bar(n), 1); +12: EXPECT_EQ(Bar(n + 1), 2); +13: } +14: +15: TEST(FooTest, Bar) { +16: { +17: SCOPED_TRACE("A"); // This trace point will be included in +18: // every failure in this scope. +19: Sub1(1); +20: } +21: // Now it won't. +22: Sub1(9); +23: } +``` + +could result in messages like these: + +```none +path/to/foo_test.cc:11: Failure +Value of: Bar(n) +Expected: 1 + Actual: 2 +Google Test trace: +path/to/foo_test.cc:17: A + +path/to/foo_test.cc:12: Failure +Value of: Bar(n + 1) +Expected: 2 + Actual: 3 +``` + +Without the trace, it would've been difficult to know which invocation of +`Sub1()` the two failures come from respectively. (You could add an extra +message to each assertion in `Sub1()` to indicate the value of `n`, but that's +tedious.) + +Some tips on using `SCOPED_TRACE`: + +1. With a suitable message, it's often enough to use `SCOPED_TRACE` at the + beginning of a sub-routine, instead of at each call site. +2. When calling sub-routines inside a loop, make the loop iterator part of the + message in `SCOPED_TRACE` such that you can know which iteration the failure + is from. +3. Sometimes the line number of the trace point is enough for identifying the + particular invocation of a sub-routine. In this case, you don't have to + choose a unique message for `SCOPED_TRACE`. You can simply use `""`. +4. You can use `SCOPED_TRACE` in an inner scope when there is one in the outer + scope. In this case, all active trace points will be included in the failure + messages, in reverse order they are encountered. +5. The trace dump is clickable in Emacs - hit `return` on a line number and + you'll be taken to that line in the source file! + +### Propagating Fatal Failures + +A common pitfall when using `ASSERT_*` and `FAIL*` is not understanding that +when they fail they only abort the _current function_, not the entire test. For +example, the following test will segfault: + +```c++ +void Subroutine() { + // Generates a fatal failure and aborts the current function. + ASSERT_EQ(1, 2); + + // The following won't be executed. + ... +} + +TEST(FooTest, Bar) { + Subroutine(); // The intended behavior is for the fatal failure + // in Subroutine() to abort the entire test. + + // The actual behavior: the function goes on after Subroutine() returns. + int* p = nullptr; + *p = 3; // Segfault! +} +``` + +To alleviate this, GoogleTest provides three different solutions. You could use +either exceptions, the `(ASSERT|EXPECT)_NO_FATAL_FAILURE` assertions or the +`HasFatalFailure()` function. They are described in the following two +subsections. + +#### Asserting on Subroutines with an exception + +The following code can turn ASSERT-failure into an exception: + +```c++ +class ThrowListener : public testing::EmptyTestEventListener { + void OnTestPartResult(const testing::TestPartResult& result) override { + if (result.type() == testing::TestPartResult::kFatalFailure) { + throw testing::AssertionException(result); + } + } +}; +int main(int argc, char** argv) { + ... + testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); + return RUN_ALL_TESTS(); +} +``` + +This listener should be added after other listeners if you have any, otherwise +they won't see failed `OnTestPartResult`. + +#### Asserting on Subroutines + +As shown above, if your test calls a subroutine that has an `ASSERT_*` failure +in it, the test will continue after the subroutine returns. This may not be what +you want. + +Often people want fatal failures to propagate like exceptions. For that +GoogleTest offers the following macros: + +Fatal assertion | Nonfatal assertion | Verifies +------------------------------------- | ------------------------------------- | -------- +`ASSERT_NO_FATAL_FAILURE(statement);` | `EXPECT_NO_FATAL_FAILURE(statement);` | `statement` doesn't generate any new fatal failures in the current thread. + +Only failures in the thread that executes the assertion are checked to determine +the result of this type of assertions. If `statement` creates new threads, +failures in these threads are ignored. + +Examples: + +```c++ +ASSERT_NO_FATAL_FAILURE(Foo()); + +int i; +EXPECT_NO_FATAL_FAILURE({ + i = Bar(); +}); +``` + +Assertions from multiple threads are currently not supported on Windows. + +#### Checking for Failures in the Current Test + +`HasFatalFailure()` in the `::testing::Test` class returns `true` if an +assertion in the current test has suffered a fatal failure. This allows +functions to catch fatal failures in a sub-routine and return early. + +```c++ +class Test { + public: + ... + static bool HasFatalFailure(); +}; +``` + +The typical usage, which basically simulates the behavior of a thrown exception, +is: + +```c++ +TEST(FooTest, Bar) { + Subroutine(); + // Aborts if Subroutine() had a fatal failure. + if (HasFatalFailure()) return; + + // The following won't be executed. + ... +} +``` + +If `HasFatalFailure()` is used outside of `TEST()` , `TEST_F()` , or a test +fixture, you must add the `::testing::Test::` prefix, as in: + +```c++ +if (testing::Test::HasFatalFailure()) return; +``` + +Similarly, `HasNonfatalFailure()` returns `true` if the current test has at +least one non-fatal failure, and `HasFailure()` returns `true` if the current +test has at least one failure of either kind. + +## Logging Additional Information + +In your test code, you can call `RecordProperty("key", value)` to log additional +information, where `value` can be either a string or an `int`. The *last* value +recorded for a key will be emitted to the +[XML output](#generating-an-xml-report) if you specify one. For example, the +test + +```c++ +TEST_F(WidgetUsageTest, MinAndMaxWidgets) { + RecordProperty("MaximumWidgets", ComputeMaxUsage()); + RecordProperty("MinimumWidgets", ComputeMinUsage()); +} +``` + +will output XML like this: + +```xml + ... + + ... +``` + +{: .callout .note} +> NOTE: +> +> * `RecordProperty()` is a static member of the `Test` class. Therefore it +> needs to be prefixed with `::testing::Test::` if used outside of the +> `TEST` body and the test fixture class. +> * *`key`* must be a valid XML attribute name, and cannot conflict with the +> ones already used by GoogleTest (`name`, `status`, `time`, `classname`, +> `type_param`, and `value_param`). +> * Calling `RecordProperty()` outside of the lifespan of a test is allowed. +> If it's called outside of a test but between a test suite's +> `SetUpTestSuite()` and `TearDownTestSuite()` methods, it will be +> attributed to the XML element for the test suite. If it's called outside +> of all test suites (e.g. in a test environment), it will be attributed to +> the top-level XML element. + +## Sharing Resources Between Tests in the Same Test Suite + +GoogleTest creates a new test fixture object for each test in order to make +tests independent and easier to debug. However, sometimes tests use resources +that are expensive to set up, making the one-copy-per-test model prohibitively +expensive. + +If the tests don't change the resource, there's no harm in their sharing a +single resource copy. So, in addition to per-test set-up/tear-down, GoogleTest +also supports per-test-suite set-up/tear-down. To use it: + +1. In your test fixture class (say `FooTest` ), declare as `static` some member + variables to hold the shared resources. +2. Outside your test fixture class (typically just below it), define those + member variables, optionally giving them initial values. +3. In the same test fixture class, define a `static void SetUpTestSuite()` + function (remember not to spell it as **`SetupTestSuite`** with a small + `u`!) to set up the shared resources and a `static void TearDownTestSuite()` + function to tear them down. + +That's it! GoogleTest automatically calls `SetUpTestSuite()` before running the +*first test* in the `FooTest` test suite (i.e. before creating the first +`FooTest` object), and calls `TearDownTestSuite()` after running the *last test* +in it (i.e. after deleting the last `FooTest` object). In between, the tests can +use the shared resources. + +Remember that the test order is undefined, so your code can't depend on a test +preceding or following another. Also, the tests must either not modify the state +of any shared resource, or, if they do modify the state, they must restore the +state to its original value before passing control to the next test. + +Note that `SetUpTestSuite()` may be called multiple times for a test fixture +class that has derived classes, so you should not expect code in the function +body to be run only once. Also, derived classes still have access to shared +resources defined as static members, so careful consideration is needed when +managing shared resources to avoid memory leaks if shared resources are not +properly cleaned up in `TearDownTestSuite()`. + +Here's an example of per-test-suite set-up and tear-down: + +```c++ +class FooTest : public testing::Test { + protected: + // Per-test-suite set-up. + // Called before the first test in this test suite. + // Can be omitted if not needed. + static void SetUpTestSuite() { + shared_resource_ = new ...; + + // If `shared_resource_` is **not deleted** in `TearDownTestSuite()`, + // reallocation should be prevented because `SetUpTestSuite()` may be called + // in subclasses of FooTest and lead to memory leak. + // + // if (shared_resource_ == nullptr) { + // shared_resource_ = new ...; + // } + } + + // Per-test-suite tear-down. + // Called after the last test in this test suite. + // Can be omitted if not needed. + static void TearDownTestSuite() { + delete shared_resource_; + shared_resource_ = nullptr; + } + + // You can define per-test set-up logic as usual. + void SetUp() override { ... } + + // You can define per-test tear-down logic as usual. + void TearDown() override { ... } + + // Some expensive resource shared by all tests. + static T* shared_resource_; +}; + +T* FooTest::shared_resource_ = nullptr; + +TEST_F(FooTest, Test1) { + ... you can refer to shared_resource_ here ... +} + +TEST_F(FooTest, Test2) { + ... you can refer to shared_resource_ here ... +} +``` + +{: .callout .note} +NOTE: Though the above code declares `SetUpTestSuite()` protected, it may +sometimes be necessary to declare it public, such as when using it with +`TEST_P`. + +## Global Set-Up and Tear-Down + +Just as you can do set-up and tear-down at the test level and the test suite +level, you can also do it at the test program level. Here's how. + +First, you subclass the `::testing::Environment` class to define a test +environment, which knows how to set-up and tear-down: + +```c++ +class Environment : public ::testing::Environment { + public: + ~Environment() override {} + + // Override this to define how to set up the environment. + void SetUp() override {} + + // Override this to define how to tear down the environment. + void TearDown() override {} +}; +``` + +Then, you register an instance of your environment class with GoogleTest by +calling the `::testing::AddGlobalTestEnvironment()` function: + +```c++ +Environment* AddGlobalTestEnvironment(Environment* env); +``` + +Now, when `RUN_ALL_TESTS()` is called, it first calls the `SetUp()` method of +each environment object, then runs the tests if none of the environments +reported fatal failures and `GTEST_SKIP()` was not called. `RUN_ALL_TESTS()` +always calls `TearDown()` with each environment object, regardless of whether or +not the tests were run. + +It's OK to register multiple environment objects. In this suite, their `SetUp()` +will be called in the order they are registered, and their `TearDown()` will be +called in the reverse order. + +Note that GoogleTest takes ownership of the registered environment objects. +Therefore **do not delete them** by yourself. + +You should call `AddGlobalTestEnvironment()` before `RUN_ALL_TESTS()` is called, +probably in `main()`. If you use `gtest_main`, you need to call this before +`main()` starts for it to take effect. One way to do this is to define a global +variable like this: + +```c++ +testing::Environment* const foo_env = + testing::AddGlobalTestEnvironment(new FooEnvironment); +``` + +However, we strongly recommend you to write your own `main()` and call +`AddGlobalTestEnvironment()` there, as relying on initialization of global +variables makes the code harder to read and may cause problems when you register +multiple environments from different translation units and the environments have +dependencies among them (remember that the compiler doesn't guarantee the order +in which global variables from different translation units are initialized). + +## Value-Parameterized Tests + +*Value-parameterized tests* allow you to test your code with different +parameters without writing multiple copies of the same test. This is useful in a +number of situations, for example: + +* You have a piece of code whose behavior is affected by one or more + command-line flags. You want to make sure your code performs correctly for + various values of those flags. +* You want to test different implementations of an OO interface. +* You want to test your code over various inputs (a.k.a. data-driven testing). + This feature is easy to abuse, so please exercise your good sense when doing + it! + +### How to Write Value-Parameterized Tests + +To write value-parameterized tests, first you should define a fixture class. It +must be derived from both `testing::Test` and `testing::WithParamInterface` +(the latter is a pure interface), where `T` is the type of your parameter +values. For convenience, you can just derive the fixture class from +`testing::TestWithParam`, which itself is derived from both `testing::Test` +and `testing::WithParamInterface`. `T` can be any copyable type. If it's a +raw pointer, you are responsible for managing the lifespan of the pointed +values. + +{: .callout .note} +NOTE: If your test fixture defines `SetUpTestSuite()` or `TearDownTestSuite()` +they must be declared **public** rather than **protected** in order to use +`TEST_P`. + +```c++ +class FooTest : + public testing::TestWithParam { + // You can implement all the usual fixture class members here. + // To access the test parameter, call GetParam() from class + // TestWithParam. +}; + +// Or, when you want to add parameters to a pre-existing fixture class: +class BaseTest : public testing::Test { + ... +}; +class BarTest : public BaseTest, + public testing::WithParamInterface { + ... +}; +``` + +Then, use the `TEST_P` macro to define as many test patterns using this fixture +as you want. The `_P` suffix is for "parameterized" or "pattern", whichever you +prefer to think. + +```c++ +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} +``` + +Finally, you can use the `INSTANTIATE_TEST_SUITE_P` macro to instantiate the +test suite with any set of parameters you want. GoogleTest defines a number of +functions for generating test parameters—see details at +[`INSTANTIATE_TEST_SUITE_P`](reference/testing.md#INSTANTIATE_TEST_SUITE_P) in +the Testing Reference. + +For example, the following statement will instantiate tests from the `FooTest` +test suite each with parameter values `"meeny"`, `"miny"`, and `"moe"` using the +[`Values`](reference/testing.md#param-generators) parameter generator: + +```c++ +INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, + FooTest, + testing::Values("meeny", "miny", "moe")); +``` + +{: .callout .note} +NOTE: The code above must be placed at global or namespace scope, not at +function scope. + +The first argument to `INSTANTIATE_TEST_SUITE_P` is a unique name for the +instantiation of the test suite. The next argument is the name of the test +pattern, and the last is the +[parameter generator](reference/testing.md#param-generators). + +The parameter generator expression is not evaluated until GoogleTest is +initialized (via `InitGoogleTest()`). Any prior initialization done in the +`main` function will be accessible from the parameter generator, for example, +the results of flag parsing. + +You can instantiate a test pattern more than once, so to distinguish different +instances of the pattern, the instantiation name is added as a prefix to the +actual test suite name. Remember to pick unique prefixes for different +instantiations. The tests from the instantiation above will have these names: + +* `MeenyMinyMoe/FooTest.DoesBlah/0` for `"meeny"` +* `MeenyMinyMoe/FooTest.DoesBlah/1` for `"miny"` +* `MeenyMinyMoe/FooTest.DoesBlah/2` for `"moe"` +* `MeenyMinyMoe/FooTest.HasBlahBlah/0` for `"meeny"` +* `MeenyMinyMoe/FooTest.HasBlahBlah/1` for `"miny"` +* `MeenyMinyMoe/FooTest.HasBlahBlah/2` for `"moe"` + +You can use these names in [`--gtest_filter`](#running-a-subset-of-the-tests). + +The following statement will instantiate all tests from `FooTest` again, each +with parameter values `"cat"` and `"dog"` using the +[`ValuesIn`](reference/testing.md#param-generators) parameter generator: + +```c++ +constexpr absl::string_view kPets[] = {"cat", "dog"}; +INSTANTIATE_TEST_SUITE_P(Pets, FooTest, testing::ValuesIn(kPets)); +``` + +The tests from the instantiation above will have these names: + +* `Pets/FooTest.DoesBlah/0` for `"cat"` +* `Pets/FooTest.DoesBlah/1` for `"dog"` +* `Pets/FooTest.HasBlahBlah/0` for `"cat"` +* `Pets/FooTest.HasBlahBlah/1` for `"dog"` + +Please note that `INSTANTIATE_TEST_SUITE_P` will instantiate *all* tests in the +given test suite, whether their definitions come before or *after* the +`INSTANTIATE_TEST_SUITE_P` statement. + +Additionally, by default, every `TEST_P` without a corresponding +`INSTANTIATE_TEST_SUITE_P` causes a failing test in test suite +`GoogleTestVerification`. If you have a test suite where that omission is not an +error, for example it is in a library that may be linked in for other reasons or +where the list of test cases is dynamic and may be empty, then this check can be +suppressed by tagging the test suite: + +```c++ +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FooTest); +``` + +You can see [sample7_unittest.cc] and [sample8_unittest.cc] for more examples. + +[sample7_unittest.cc]: https://github.com/google/googletest/blob/main/googletest/samples/sample7_unittest.cc "Parameterized Test example" +[sample8_unittest.cc]: https://github.com/google/googletest/blob/main/googletest/samples/sample8_unittest.cc "Parameterized Test example with multiple parameters" + +### Creating Value-Parameterized Abstract Tests + +In the above, we define and instantiate `FooTest` in the *same* source file. +Sometimes you may want to define value-parameterized tests in a library and let +other people instantiate them later. This pattern is known as *abstract tests*. +As an example of its application, when you are designing an interface you can +write a standard suite of abstract tests (perhaps using a factory function as +the test parameter) that all implementations of the interface are expected to +pass. When someone implements the interface, they can instantiate your suite to +get all the interface-conformance tests for free. + +To define abstract tests, you should organize your code like this: + +1. Put the definition of the parameterized test fixture class (e.g. `FooTest`) + in a header file, say `foo_param_test.h`. Think of this as *declaring* your + abstract tests. +2. Put the `TEST_P` definitions in `foo_param_test.cc`, which includes + `foo_param_test.h`. Think of this as *implementing* your abstract tests. + +Once they are defined, you can instantiate them by including `foo_param_test.h`, +invoking `INSTANTIATE_TEST_SUITE_P()`, and depending on the library target that +contains `foo_param_test.cc`. You can instantiate the same abstract test suite +multiple times, possibly in different source files. + +### Specifying Names for Value-Parameterized Test Parameters + +The optional last argument to `INSTANTIATE_TEST_SUITE_P()` allows the user to +specify a function or functor that generates custom test name suffixes based on +the test parameters. The function should accept one argument of type +`testing::TestParamInfo`, and return `std::string`. + +`testing::PrintToStringParamName` is a builtin test suffix generator that +returns the value of `testing::PrintToString(GetParam())`. It does not work for +`std::string` or C strings. + +{: .callout .note} +NOTE: test names must be non-empty, unique, and may only contain ASCII +alphanumeric characters. In particular, they +[should not contain underscores](faq.md#why-should-test-suite-names-and-test-names-not-contain-underscore) + +```c++ +class MyTestSuite : public testing::TestWithParam {}; + +TEST_P(MyTestSuite, MyTest) +{ + std::cout << "Example Test Param: " << GetParam() << std::endl; +} + +INSTANTIATE_TEST_SUITE_P(MyGroup, MyTestSuite, testing::Range(0, 10), + testing::PrintToStringParamName()); +``` + +Providing a custom functor allows for more control over test parameter name +generation, especially for types where the automatic conversion does not +generate helpful parameter names (e.g. strings as demonstrated above). The +following example illustrates this for multiple parameters, an enumeration type +and a string, and also demonstrates how to combine generators. It uses a lambda +for conciseness: + +```c++ +enum class MyType { MY_FOO = 0, MY_BAR = 1 }; + +class MyTestSuite : public testing::TestWithParam> { +}; + +INSTANTIATE_TEST_SUITE_P( + MyGroup, MyTestSuite, + testing::Combine( + testing::Values(MyType::MY_FOO, MyType::MY_BAR), + testing::Values("A", "B")), + [](const testing::TestParamInfo& info) { + std::string name = absl::StrCat( + std::get<0>(info.param) == MyType::MY_FOO ? "Foo" : "Bar", + std::get<1>(info.param)); + absl::c_replace_if(name, [](char c) { return !std::isalnum(c); }, '_'); + return name; + }); +``` + +## Typed Tests + +Suppose you have multiple implementations of the same interface and want to make +sure that all of them satisfy some common requirements. Or, you may have defined +several types that are supposed to conform to the same "concept" and you want to +verify it. In both cases, you want the same test logic repeated for different +types. + +While you can write one `TEST` or `TEST_F` for each type you want to test (and +you may even factor the test logic into a function template that you invoke from +the `TEST`), it's tedious and doesn't scale: if you want `m` tests over `n` +types, you'll end up writing `m*n` `TEST`s. + +*Typed tests* allow you to repeat the same test logic over a list of types. You +only need to write the test logic once, although you must know the type list +when writing typed tests. Here's how you do it: + +First, define a fixture class template. It should be parameterized by a type. +Remember to derive it from `::testing::Test`: + +```c++ +template +class FooTest : public testing::Test { + public: + ... + using List = std::list; + static T shared_; + T value_; +}; +``` + +Next, associate a list of types with the test suite, which will be repeated for +each type in the list: + +```c++ +using MyTypes = ::testing::Types; +TYPED_TEST_SUITE(FooTest, MyTypes); +``` + +The type alias (`using` or `typedef`) is necessary for the `TYPED_TEST_SUITE` +macro to parse correctly. Otherwise the compiler will think that each comma in +the type list introduces a new macro argument. + +Then, use `TYPED_TEST()` instead of `TEST_F()` to define a typed test for this +test suite. You can repeat this as many times as you want: + +```c++ +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to the special name TypeParam to get the type + // parameter. Since we are inside a derived class template, C++ requires + // us to visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the 'TestFixture::' + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the 'typename TestFixture::' + // prefix. The 'typename' is required to satisfy the compiler. + typename TestFixture::List values; + + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } +``` + +You can see [sample6_unittest.cc] for a complete example. + +[sample6_unittest.cc]: https://github.com/google/googletest/blob/main/googletest/samples/sample6_unittest.cc "Typed Test example" + +## Type-Parameterized Tests + +*Type-parameterized tests* are like typed tests, except that they don't require +you to know the list of types ahead of time. Instead, you can define the test +logic first and instantiate it with different type lists later. You can even +instantiate it more than once in the same program. + +If you are designing an interface or concept, you can define a suite of +type-parameterized tests to verify properties that any valid implementation of +the interface/concept should have. Then, the author of each implementation can +just instantiate the test suite with their type to verify that it conforms to +the requirements, without having to write similar tests repeatedly. Here's an +example: + +First, define a fixture class template, as we did with typed tests: + +```c++ +template +class FooTest : public testing::Test { + void DoSomethingInteresting(); + ... +}; +``` + +Next, declare that you will define a type-parameterized test suite: + +```c++ +TYPED_TEST_SUITE_P(FooTest); +``` + +Then, use `TYPED_TEST_P()` to define a type-parameterized test. You can repeat +this as many times as you want: + +```c++ +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + + // You will need to use `this` explicitly to refer to fixture members. + this->DoSomethingInteresting() + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } +``` + +Now the tricky part: you need to register all test patterns using the +`REGISTER_TYPED_TEST_SUITE_P` macro before you can instantiate them. The first +argument of the macro is the test suite name; the rest are the names of the +tests in this test suite: + +```c++ +REGISTER_TYPED_TEST_SUITE_P(FooTest, + DoesBlah, HasPropertyA); +``` + +Finally, you are free to instantiate the pattern with the types you want. If you +put the above code in a header file, you can `#include` it in multiple C++ +source files and instantiate it multiple times. + +```c++ +using MyTypes = ::testing::Types; +INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes); +``` + +To distinguish different instances of the pattern, the first argument to the +`INSTANTIATE_TYPED_TEST_SUITE_P` macro is a prefix that will be added to the +actual test suite name. Remember to pick unique prefixes for different +instances. + +In the special case where the type list contains only one type, you can write +that type directly without `::testing::Types<...>`, like this: + +```c++ +INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int); +``` + +You can see [sample6_unittest.cc] for a complete example. + +## Testing Private Code + +If you change your software's internal implementation, your tests should not +break as long as the change is not observable by users. Therefore, **per the +black-box testing principle, most of the time you should test your code through +its public interfaces.** + +**If you still find yourself needing to test internal implementation code, +consider if there's a better design.** The desire to test internal +implementation is often a sign that the class is doing too much. Consider +extracting an implementation class, and testing it. Then use that implementation +class in the original class. + +If you absolutely have to test non-public interface code though, you can. There +are two cases to consider: + +* Static functions ( *not* the same as static member functions!) or unnamed + namespaces, and +* Private or protected class members + +To test them, we use the following special techniques: + +* Both static functions and definitions/declarations in an unnamed namespace + are only visible within the same translation unit. To test them, you can + `#include` the entire `.cc` file being tested in your `*_test.cc` file. + (#including `.cc` files is not a good way to reuse code - you should not do + this in production code!) + + However, a better approach is to move the private code into the + `foo::internal` namespace, where `foo` is the namespace your project + normally uses, and put the private declarations in a `*-internal.h` file. + Your production `.cc` files and your tests are allowed to include this + internal header, but your clients are not. This way, you can fully test your + internal implementation without leaking it to your clients. + +* Private class members are only accessible from within the class or by + friends. To access a class' private members, you can declare your test + fixture as a friend to the class and define accessors in your fixture. Tests + using the fixture can then access the private members of your production + class via the accessors in the fixture. Note that even though your fixture + is a friend to your production class, your tests are not automatically + friends to it, as they are technically defined in sub-classes of the + fixture. + + Another way to test private members is to refactor them into an + implementation class, which is then declared in a `*-internal.h` file. Your + clients aren't allowed to include this header but your tests can. Such is + called the + [Pimpl](https://www.gamedev.net/articles/programming/general-and-gameplay-programming/the-c-pimpl-r1794/) + (Private Implementation) idiom. + + Or, you can declare an individual test as a friend of your class by adding + this line in the class body: + + ```c++ + FRIEND_TEST(TestSuiteName, TestName); + ``` + + For example, + + ```c++ + // foo.h + class Foo { + ... + private: + FRIEND_TEST(FooTest, BarReturnsZeroOnNull); + + int Bar(void* x); + }; + + // foo_test.cc + ... + TEST(FooTest, BarReturnsZeroOnNull) { + Foo foo; + EXPECT_EQ(foo.Bar(NULL), 0); // Uses Foo's private member Bar(). + } + ``` + + Pay special attention when your class is defined in a namespace. If you want + your test fixtures and tests to be friends of your class, then they must be + defined in the exact same namespace (no anonymous or inline namespaces). + + For example, if the code to be tested looks like: + + ```c++ + namespace my_namespace { + + class Foo { + friend class FooTest; + FRIEND_TEST(FooTest, Bar); + FRIEND_TEST(FooTest, Baz); + ... definition of the class Foo ... + }; + + } // namespace my_namespace + ``` + + Your test code should be something like: + + ```c++ + namespace my_namespace { + + class FooTest : public testing::Test { + protected: + ... + }; + + TEST_F(FooTest, Bar) { ... } + TEST_F(FooTest, Baz) { ... } + + } // namespace my_namespace + ``` + +## "Catching" Failures + +If you are building a testing utility on top of GoogleTest, you'll want to test +your utility. What framework would you use to test it? GoogleTest, of course. + +The challenge is to verify that your testing utility reports failures correctly. +In frameworks that report a failure by throwing an exception, you could catch +the exception and assert on it. But GoogleTest doesn't use exceptions, so how do +we test that a piece of code generates an expected failure? + +`"gtest/gtest-spi.h"` contains some constructs to do this. +After #including this header, you can use + +```c++ + EXPECT_FATAL_FAILURE(statement, substring); +``` + +to assert that `statement` generates a fatal (e.g. `ASSERT_*`) failure in the +current thread whose message contains the given `substring`, or use + +```c++ + EXPECT_NONFATAL_FAILURE(statement, substring); +``` + +if you are expecting a non-fatal (e.g. `EXPECT_*`) failure. + +Only failures in the current thread are checked to determine the result of this +type of expectations. If `statement` creates new threads, failures in these +threads are also ignored. If you want to catch failures in other threads as +well, use one of the following macros instead: + +```c++ + EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substring); + EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substring); +``` + +{: .callout .note} +NOTE: Assertions from multiple threads are currently not supported on Windows. + +For technical reasons, there are some caveats: + +1. You cannot stream a failure message to either macro. + +2. `statement` in `EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}()` cannot reference + local non-static variables or non-static members of `this` object. + +3. `statement` in `EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}()` cannot return a + value. + +## Registering tests programmatically + +The `TEST` macros handle the vast majority of all use cases, but there are few +where runtime registration logic is required. For those cases, the framework +provides the `::testing::RegisterTest` that allows callers to register arbitrary +tests dynamically. + +This is an advanced API only to be used when the `TEST` macros are insufficient. +The macros should be preferred when possible, as they avoid most of the +complexity of calling this function. + +It provides the following signature: + +```c++ +template +TestInfo* RegisterTest(const char* test_suite_name, const char* test_name, + const char* type_param, const char* value_param, + const char* file, int line, Factory factory); +``` + +The `factory` argument is a factory callable (move-constructible) object or +function pointer that creates a new instance of the Test object. It handles +ownership to the caller. The signature of the callable is `Fixture*()`, where +`Fixture` is the test fixture class for the test. All tests registered with the +same `test_suite_name` must return the same fixture type. This is checked at +runtime. + +The framework will infer the fixture class from the factory and will call the +`SetUpTestSuite` and `TearDownTestSuite` for it. + +Must be called before `RUN_ALL_TESTS()` is invoked, otherwise behavior is +undefined. + +Use case example: + +```c++ +class MyFixture : public testing::Test { + public: + // All of these optional, just like in regular macro usage. + static void SetUpTestSuite() { ... } + static void TearDownTestSuite() { ... } + void SetUp() override { ... } + void TearDown() override { ... } +}; + +class MyTest : public MyFixture { + public: + explicit MyTest(int data) : data_(data) {} + void TestBody() override { ... } + + private: + int data_; +}; + +void RegisterMyTests(const std::vector& values) { + for (int v : values) { + testing::RegisterTest( + "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr, + std::to_string(v).c_str(), + __FILE__, __LINE__, + // Important to use the fixture type as the return type here. + [=]() -> MyFixture* { return new MyTest(v); }); + } +} +... +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + std::vector values_to_test = LoadValuesFromConfig(); + RegisterMyTests(values_to_test); + ... + return RUN_ALL_TESTS(); +} +``` + +## Getting the Current Test's Name + +Sometimes a function may need to know the name of the currently running test. +For example, you may be using the `SetUp()` method of your test fixture to set +the golden file name based on which test is running. The +[`TestInfo`](reference/testing.md#TestInfo) class has this information. + +To obtain a `TestInfo` object for the currently running test, call +`current_test_info()` on the [`UnitTest`](reference/testing.md#UnitTest) +singleton object: + +```c++ + // Gets information about the currently running test. + // Do NOT delete the returned object - it's managed by the UnitTest class. + const testing::TestInfo* const test_info = + testing::UnitTest::GetInstance()->current_test_info(); + + printf("We are in test %s of test suite %s.\n", + test_info->name(), + test_info->test_suite_name()); +``` + +`current_test_info()` returns a null pointer if no test is running. In +particular, you cannot find the test suite name in `SetUpTestSuite()`, +`TearDownTestSuite()` (where you know the test suite name implicitly), or +functions called from them. + +## Extending GoogleTest by Handling Test Events + +GoogleTest provides an **event listener API** to let you receive notifications +about the progress of a test program and test failures. The events you can +listen to include the start and end of the test program, a test suite, or a test +method, among others. You may use this API to augment or replace the standard +console output, replace the XML output, or provide a completely different form +of output, such as a GUI or a database. You can also use test events as +checkpoints to implement a resource leak checker, for example. + +### Defining Event Listeners + +To define a event listener, you subclass either +[`testing::TestEventListener`](reference/testing.md#TestEventListener) or +[`testing::EmptyTestEventListener`](reference/testing.md#EmptyTestEventListener) +The former is an (abstract) interface, where *each pure virtual method can be +overridden to handle a test event* (For example, when a test starts, the +`OnTestStart()` method will be called.). The latter provides an empty +implementation of all methods in the interface, such that a subclass only needs +to override the methods it cares about. + +When an event is fired, its context is passed to the handler function as an +argument. The following argument types are used: + +* UnitTest reflects the state of the entire test program, +* TestSuite has information about a test suite, which can contain one or more + tests, +* TestInfo contains the state of a test, and +* TestPartResult represents the result of a test assertion. + +An event handler function can examine the argument it receives to find out +interesting information about the event and the test program's state. + +Here's an example: + +```c++ + class MinimalistPrinter : public testing::EmptyTestEventListener { + // Called before a test starts. + void OnTestStart(const testing::TestInfo& test_info) override { + printf("*** Test %s.%s starting.\n", + test_info.test_suite_name(), test_info.name()); + } + + // Called after a failed assertion or a SUCCESS(). + void OnTestPartResult(const testing::TestPartResult& test_part_result) override { + printf("%s in %s:%d\n%s\n", + test_part_result.failed() ? "*** Failure" : "Success", + test_part_result.file_name(), + test_part_result.line_number(), + test_part_result.summary()); + } + + // Called after a test ends. + void OnTestEnd(const testing::TestInfo& test_info) override { + printf("*** Test %s.%s ending.\n", + test_info.test_suite_name(), test_info.name()); + } + }; +``` + +### Using Event Listeners + +To use the event listener you have defined, add an instance of it to the +GoogleTest event listener list (represented by class +[`TestEventListeners`](reference/testing.md#TestEventListeners) - note the "s" +at the end of the name) in your `main()` function, before calling +`RUN_ALL_TESTS()`: + +```c++ +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + // Gets hold of the event listener list. + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + // Adds a listener to the end. GoogleTest takes the ownership. + listeners.Append(new MinimalistPrinter); + return RUN_ALL_TESTS(); +} +``` + +There's only one problem: the default test result printer is still in effect, so +its output will mingle with the output from your minimalist printer. To suppress +the default printer, just release it from the event listener list and delete it. +You can do so by adding one line: + +```c++ + ... + delete listeners.Release(listeners.default_result_printer()); + listeners.Append(new MinimalistPrinter); + return RUN_ALL_TESTS(); +``` + +Now, sit back and enjoy a completely different output from your tests. For more +details, see [sample9_unittest.cc]. + +[sample9_unittest.cc]: https://github.com/google/googletest/blob/main/googletest/samples/sample9_unittest.cc "Event listener example" + +You may append more than one listener to the list. When an `On*Start()` or +`OnTestPartResult()` event is fired, the listeners will receive it in the order +they appear in the list (since new listeners are added to the end of the list, +the default text printer and the default XML generator will receive the event +first). An `On*End()` event will be received by the listeners in the *reverse* +order. This allows output by listeners added later to be framed by output from +listeners added earlier. + +### Generating Failures in Listeners + +You may use failure-raising macros (`EXPECT_*()`, `ASSERT_*()`, `FAIL()`, etc) +when processing an event. There are some restrictions: + +1. You cannot generate any failure in `OnTestPartResult()` (otherwise it will + cause `OnTestPartResult()` to be called recursively). +2. A listener that handles `OnTestPartResult()` is not allowed to generate any + failure. + +When you add listeners to the listener list, you should put listeners that +handle `OnTestPartResult()` *before* listeners that can generate failures. This +ensures that failures generated by the latter are attributed to the right test +by the former. + +See [sample10_unittest.cc] for an example of a failure-raising listener. + +[sample10_unittest.cc]: https://github.com/google/googletest/blob/main/googletest/samples/sample10_unittest.cc "Failure-raising listener example" + +## Running Test Programs: Advanced Options + +GoogleTest test programs are ordinary executables. Once built, you can run them +directly and affect their behavior via the following environment variables +and/or command line flags. For the flags to work, your programs must call +`::testing::InitGoogleTest()` before calling `RUN_ALL_TESTS()`. + +To see a list of supported flags and their usage, please run your test program +with the `--help` flag. You can also use `-h`, `-?`, or `/?` for short. + +If an option is specified both by an environment variable and by a flag, the +latter takes precedence. + +### Selecting Tests + +#### Listing Test Names + +Sometimes it is necessary to list the available tests in a program before +running them so that a filter may be applied if needed. Including the flag +`--gtest_list_tests` overrides all other flags and lists tests in the following +format: + +```none +TestSuite1. + TestName1 + TestName2 +TestSuite2. + TestName +``` + +None of the tests listed are actually run if the flag is provided. There is no +corresponding environment variable for this flag. + +#### Running a Subset of the Tests + +By default, a GoogleTest program runs all tests the user has defined. Sometimes, +you want to run only a subset of the tests (e.g. for debugging or quickly +verifying a change). If you set the `GTEST_FILTER` environment variable or the +`--gtest_filter` flag to a filter string, GoogleTest will only run the tests +whose full names (in the form of `TestSuiteName.TestName`) match the filter. + +The format of a filter is a '`:`'-separated list of wildcard patterns (called +the *positive patterns*) optionally followed by a '`-`' and another +'`:`'-separated pattern list (called the *negative patterns*). A test matches +the filter if and only if it matches any of the positive patterns but does not +match any of the negative patterns. + +A pattern may contain `'*'` (matches any string) or `'?'` (matches any single +character). For convenience, the filter `'*-NegativePatterns'` can be also +written as `'-NegativePatterns'`. + +For example: + +* `./foo_test` Has no flag, and thus runs all its tests. +* `./foo_test --gtest_filter=*` Also runs everything, due to the single + match-everything `*` value. +* `./foo_test --gtest_filter=FooTest.*` Runs everything in test suite + `FooTest` . +* `./foo_test --gtest_filter=*Null*:*Constructor*` Runs any test whose full + name contains either `"Null"` or `"Constructor"` . +* `./foo_test --gtest_filter=-*DeathTest.*` Runs all non-death tests. +* `./foo_test --gtest_filter=FooTest.*-FooTest.Bar` Runs everything in test + suite `FooTest` except `FooTest.Bar`. +* `./foo_test --gtest_filter=FooTest.*:BarTest.*-FooTest.Bar:BarTest.Foo` Runs + everything in test suite `FooTest` except `FooTest.Bar` and everything in + test suite `BarTest` except `BarTest.Foo`. + +#### Stop test execution upon first failure + +By default, a GoogleTest program runs all tests the user has defined. In some +cases (e.g. iterative test development & execution) it may be desirable stop +test execution upon first failure (trading improved latency for completeness). +If `GTEST_FAIL_FAST` environment variable or `--gtest_fail_fast` flag is set, +the test runner will stop execution as soon as the first test failure is found. + +#### Temporarily Disabling Tests + +If you have a broken test that you cannot fix right away, you can add the +`DISABLED_` prefix to its name. This will exclude it from execution. This is +better than commenting out the code or using `#if 0`, as disabled tests are +still compiled (and thus won't rot). + +If you need to disable all tests in a test suite, you can either add `DISABLED_` +to the front of the name of each test, or alternatively add it to the front of +the test suite name. + +For example, the following tests won't be run by GoogleTest, even though they +will still be compiled: + +```c++ +// Tests that Foo does Abc. +TEST(FooTest, DISABLED_DoesAbc) { ... } + +class DISABLED_BarTest : public testing::Test { ... }; + +// Tests that Bar does Xyz. +TEST_F(DISABLED_BarTest, DoesXyz) { ... } +``` + +{: .callout .note} +NOTE: This feature should only be used for temporary pain-relief. You still have +to fix the disabled tests at a later date. As a reminder, GoogleTest will print +a banner warning you if a test program contains any disabled tests. + +{: .callout .tip} +TIP: You can easily count the number of disabled tests you have using +`grep`. This number can be used as a metric for +improving your test quality. + +#### Temporarily Enabling Disabled Tests + +To include disabled tests in test execution, just invoke the test program with +the `--gtest_also_run_disabled_tests` flag or set the +`GTEST_ALSO_RUN_DISABLED_TESTS` environment variable to a value other than `0`. +You can combine this with the `--gtest_filter` flag to further select which +disabled tests to run. + +### Repeating the Tests + +Once in a while you'll run into a test whose result is hit-or-miss. Perhaps it +will fail only 1% of the time, making it rather hard to reproduce the bug under +a debugger. This can be a major source of frustration. + +The `--gtest_repeat` flag allows you to repeat all (or selected) test methods in +a program many times. Hopefully, a flaky test will eventually fail and give you +a chance to debug. Here's how to use it: + +```none +$ foo_test --gtest_repeat=1000 +Repeat foo_test 1000 times and don't stop at failures. + +$ foo_test --gtest_repeat=-1 +A negative count means repeating forever. + +$ foo_test --gtest_repeat=1000 --gtest_break_on_failure +Repeat foo_test 1000 times, stopping at the first failure. This +is especially useful when running under a debugger: when the test +fails, it will drop into the debugger and you can then inspect +variables and stacks. + +$ foo_test --gtest_repeat=1000 --gtest_filter=FooBar.* +Repeat the tests whose name matches the filter 1000 times. +``` + +If your test program contains +[global set-up/tear-down](#global-set-up-and-tear-down) code, it will be +repeated in each iteration as well, as the flakiness may be in it. To avoid +repeating global set-up/tear-down, specify +`--gtest_recreate_environments_when_repeating=false`{.nowrap}. + +You can also specify the repeat count by setting the `GTEST_REPEAT` environment +variable. + +### Shuffling the Tests + +You can specify the `--gtest_shuffle` flag (or set the `GTEST_SHUFFLE` +environment variable to `1`) to run the tests in a program in a random order. +This helps to reveal bad dependencies between tests. + +By default, GoogleTest uses a random seed calculated from the current time. +Therefore you'll get a different order every time. The console output includes +the random seed value, such that you can reproduce an order-related test failure +later. To specify the random seed explicitly, use the `--gtest_random_seed=SEED` +flag (or set the `GTEST_RANDOM_SEED` environment variable), where `SEED` is an +integer in the range [0, 99999]. The seed value 0 is special: it tells +GoogleTest to do the default behavior of calculating the seed from the current +time. + +If you combine this with `--gtest_repeat=N`, GoogleTest will pick a different +random seed and re-shuffle the tests in each iteration. + +### Distributing Test Functions to Multiple Machines + +If you have more than one machine you can use to run a test program, you might +want to run the test functions in parallel and get the result faster. We call +this technique *sharding*, where each machine is called a *shard*. + +GoogleTest is compatible with test sharding. To take advantage of this feature, +your test runner (not part of GoogleTest) needs to do the following: + +1. Allocate a number of machines (shards) to run the tests. +1. On each shard, set the `GTEST_TOTAL_SHARDS` environment variable to the total + number of shards. It must be the same for all shards. +1. On each shard, set the `GTEST_SHARD_INDEX` environment variable to the index + of the shard. Different shards must be assigned different indices, which + must be in the range `[0, GTEST_TOTAL_SHARDS - 1]`. +1. Run the same test program on all shards. When GoogleTest sees the above two + environment variables, it will select a subset of the test functions to run. + Across all shards, each test function in the program will be run exactly + once. +1. Wait for all shards to finish, then collect and report the results. + +Your project may have tests that were written without GoogleTest and thus don't +understand this protocol. In order for your test runner to figure out which test +supports sharding, it can set the environment variable `GTEST_SHARD_STATUS_FILE` +to a non-existent file path. If a test program supports sharding, it will create +this file to acknowledge that fact; otherwise it will not create it. The actual +contents of the file are not important at this time, although we may put some +useful information in it in the future. + +Here's an example to make it clear. Suppose you have a test program `foo_test` +that contains the following 5 test functions: + +``` +TEST(A, V) +TEST(A, W) +TEST(B, X) +TEST(B, Y) +TEST(B, Z) +``` + +Suppose you have 3 machines at your disposal. To run the test functions in +parallel, you would set `GTEST_TOTAL_SHARDS` to 3 on all machines, and set +`GTEST_SHARD_INDEX` to 0, 1, and 2 on the machines respectively. Then you would +run the same `foo_test` on each machine. + +GoogleTest reserves the right to change how the work is distributed across the +shards, but here's one possible scenario: + +* Machine #0 runs `A.V` and `B.X`. +* Machine #1 runs `A.W` and `B.Y`. +* Machine #2 runs `B.Z`. + +### Controlling Test Output + +#### Colored Terminal Output + +GoogleTest can use colors in its terminal output to make it easier to spot the +important information: + +
...
+[----------] 1 test from FooTest
+[ RUN      ] FooTest.DoesAbc
+[       OK ] FooTest.DoesAbc
+[----------] 2 tests from BarTest
+[ RUN      ] BarTest.HasXyzProperty
+[       OK ] BarTest.HasXyzProperty
+[ RUN      ] BarTest.ReturnsTrueOnSuccess
+... some error messages ...
+[   FAILED ] BarTest.ReturnsTrueOnSuccess
+...
+[==========] 30 tests from 14 test suites ran.
+[   PASSED ] 28 tests.
+[   FAILED ] 2 tests, listed below:
+[   FAILED ] BarTest.ReturnsTrueOnSuccess
+[   FAILED ] AnotherTest.DoesXyz
+
+ 2 FAILED TESTS
+
+ +You can set the `GTEST_COLOR` environment variable or the `--gtest_color` +command line flag to `yes`, `no`, or `auto` (the default) to enable colors, +disable colors, or let GoogleTest decide. When the value is `auto`, GoogleTest +will use colors if and only if the output goes to a terminal and (on non-Windows +platforms) the `TERM` environment variable is set to `xterm` or `xterm-color`. + +#### Suppressing test passes + +By default, GoogleTest prints 1 line of output for each test, indicating if it +passed or failed. To show only test failures, run the test program with +`--gtest_brief=1`, or set the GTEST_BRIEF environment variable to `1`. + +#### Suppressing the Elapsed Time + +By default, GoogleTest prints the time it takes to run each test. To disable +that, run the test program with the `--gtest_print_time=0` command line flag, or +set the GTEST_PRINT_TIME environment variable to `0`. + +#### Suppressing UTF-8 Text Output + +In case of assertion failures, GoogleTest prints expected and actual values of +type `string` both as hex-encoded strings as well as in readable UTF-8 text if +they contain valid non-ASCII UTF-8 characters. If you want to suppress the UTF-8 +text because, for example, you don't have an UTF-8 compatible output medium, run +the test program with `--gtest_print_utf8=0` or set the `GTEST_PRINT_UTF8` +environment variable to `0`. + +#### Generating an XML Report + +GoogleTest can emit a detailed XML report to a file in addition to its normal +textual output. The report contains the duration of each test, and thus can help +you identify slow tests. + +To generate the XML report, set the `GTEST_OUTPUT` environment variable or the +`--gtest_output` flag to the string `"xml:path_to_output_file"`, which will +create the file at the given location. You can also just use the string `"xml"`, +in which case the output can be found in the `test_detail.xml` file in the +current directory. + +If you specify a directory (for example, `"xml:output/directory/"` on Linux or +`"xml:output\directory\"` on Windows), GoogleTest will create the XML file in +that directory, named after the test executable (e.g. `foo_test.xml` for test +program `foo_test` or `foo_test.exe`). If the file already exists (perhaps left +over from a previous run), GoogleTest will pick a different name (e.g. +`foo_test_1.xml`) to avoid overwriting it. + +The report is based on the `junitreport` Ant task. Since that format was +originally intended for Java, a little interpretation is required to make it +apply to GoogleTest tests, as shown here: + +```xml + + + + + + + + + +``` + +* The root `` element corresponds to the entire test program. +* `` elements correspond to GoogleTest test suites. +* `` elements correspond to GoogleTest test functions. + +For instance, the following program + +```c++ +TEST(MathTest, Addition) { ... } +TEST(MathTest, Subtraction) { ... } +TEST(LogicTest, NonContradiction) { ... } +``` + +could generate this report: + +```xml + + + + + ... + ... + + + + + + + + + +``` + +Things to note: + +* The `tests` attribute of a `` or `` element tells how + many test functions the GoogleTest program or test suite contains, while the + `failures` attribute tells how many of them failed. + +* The `time` attribute expresses the duration of the test, test suite, or + entire test program in seconds. + +* The `timestamp` attribute records the local date and time of the test + execution. + +* The `file` and `line` attributes record the source file location, where the + test was defined. + +* Each `` element corresponds to a single failed GoogleTest + assertion. + +#### Generating a JSON Report + +GoogleTest can also emit a JSON report as an alternative format to XML. To +generate the JSON report, set the `GTEST_OUTPUT` environment variable or the +`--gtest_output` flag to the string `"json:path_to_output_file"`, which will +create the file at the given location. You can also just use the string +`"json"`, in which case the output can be found in the `test_detail.json` file +in the current directory. + +The report format conforms to the following JSON Schema: + +```json +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "definitions": { + "TestCase": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "tests": { "type": "integer" }, + "failures": { "type": "integer" }, + "disabled": { "type": "integer" }, + "time": { "type": "string" }, + "testsuite": { + "type": "array", + "items": { + "$ref": "#/definitions/TestInfo" + } + } + } + }, + "TestInfo": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "file": { "type": "string" }, + "line": { "type": "integer" }, + "status": { + "type": "string", + "enum": ["RUN", "NOTRUN"] + }, + "time": { "type": "string" }, + "classname": { "type": "string" }, + "failures": { + "type": "array", + "items": { + "$ref": "#/definitions/Failure" + } + } + } + }, + "Failure": { + "type": "object", + "properties": { + "failures": { "type": "string" }, + "type": { "type": "string" } + } + } + }, + "properties": { + "tests": { "type": "integer" }, + "failures": { "type": "integer" }, + "disabled": { "type": "integer" }, + "errors": { "type": "integer" }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "time": { "type": "string" }, + "name": { "type": "string" }, + "testsuites": { + "type": "array", + "items": { + "$ref": "#/definitions/TestCase" + } + } + } +} +``` + +The report uses the format that conforms to the following Proto3 using the +[JSON encoding](https://developers.google.com/protocol-buffers/docs/proto3#json): + +```proto +syntax = "proto3"; + +package googletest; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +message UnitTest { + int32 tests = 1; + int32 failures = 2; + int32 disabled = 3; + int32 errors = 4; + google.protobuf.Timestamp timestamp = 5; + google.protobuf.Duration time = 6; + string name = 7; + repeated TestCase testsuites = 8; +} + +message TestCase { + string name = 1; + int32 tests = 2; + int32 failures = 3; + int32 disabled = 4; + int32 errors = 5; + google.protobuf.Duration time = 6; + repeated TestInfo testsuite = 7; +} + +message TestInfo { + string name = 1; + string file = 6; + int32 line = 7; + enum Status { + RUN = 0; + NOTRUN = 1; + } + Status status = 2; + google.protobuf.Duration time = 3; + string classname = 4; + message Failure { + string failures = 1; + string type = 2; + } + repeated Failure failures = 5; +} +``` + +For instance, the following program + +```c++ +TEST(MathTest, Addition) { ... } +TEST(MathTest, Subtraction) { ... } +TEST(LogicTest, NonContradiction) { ... } +``` + +could generate this report: + +```json +{ + "tests": 3, + "failures": 1, + "errors": 0, + "time": "0.035s", + "timestamp": "2011-10-31T18:52:42Z", + "name": "AllTests", + "testsuites": [ + { + "name": "MathTest", + "tests": 2, + "failures": 1, + "errors": 0, + "time": "0.015s", + "testsuite": [ + { + "name": "Addition", + "file": "test.cpp", + "line": 1, + "status": "RUN", + "time": "0.007s", + "classname": "", + "failures": [ + { + "message": "Value of: add(1, 1)\n Actual: 3\nExpected: 2", + "type": "" + }, + { + "message": "Value of: add(1, -1)\n Actual: 1\nExpected: 0", + "type": "" + } + ] + }, + { + "name": "Subtraction", + "file": "test.cpp", + "line": 2, + "status": "RUN", + "time": "0.005s", + "classname": "" + } + ] + }, + { + "name": "LogicTest", + "tests": 1, + "failures": 0, + "errors": 0, + "time": "0.005s", + "testsuite": [ + { + "name": "NonContradiction", + "file": "test.cpp", + "line": 3, + "status": "RUN", + "time": "0.005s", + "classname": "" + } + ] + } + ] +} +``` + +{: .callout .important} +IMPORTANT: The exact format of the JSON document is subject to change. + +### Controlling How Failures Are Reported + +#### Detecting Test Premature Exit + +Google Test implements the _premature-exit-file_ protocol for test runners to +catch any kind of unexpected exits of test programs. Upon start, Google Test +creates the file which will be automatically deleted after all work has been +finished. Then, the test runner can check if this file exists. In case the file +remains undeleted, the inspected test has exited prematurely. + +This feature is enabled only if the `TEST_PREMATURE_EXIT_FILE` environment +variable has been set. + +#### Turning Assertion Failures into Break-Points + +When running test programs under a debugger, it's very convenient if the +debugger can catch an assertion failure and automatically drop into interactive +mode. GoogleTest's *break-on-failure* mode supports this behavior. + +To enable it, set the `GTEST_BREAK_ON_FAILURE` environment variable to a value +other than `0`. Alternatively, you can use the `--gtest_break_on_failure` +command line flag. + +#### Disabling Catching Test-Thrown Exceptions + +GoogleTest can be used either with or without exceptions enabled. If a test +throws a C++ exception or (on Windows) a structured exception (SEH), by default +GoogleTest catches it, reports it as a test failure, and continues with the next +test method. This maximizes the coverage of a test run. Also, on Windows an +uncaught exception will cause a pop-up window, so catching the exceptions allows +you to run the tests automatically. + +When debugging the test failures, however, you may instead want the exceptions +to be handled by the debugger, such that you can examine the call stack when an +exception is thrown. To achieve that, set the `GTEST_CATCH_EXCEPTIONS` +environment variable to `0`, or use the `--gtest_catch_exceptions=0` flag when +running the tests. + +### Sanitizer Integration + +The +[Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html), +[Address Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer), +and +[Thread Sanitizer](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual) +all provide weak functions that you can override to trigger explicit failures +when they detect sanitizer errors, such as creating a reference from `nullptr`. +To override these functions, place definitions for them in a source file that +you compile as part of your main binary: + +``` +extern "C" { +void __ubsan_on_report() { + FAIL() << "Encountered an undefined behavior sanitizer error"; +} +void __asan_on_error() { + FAIL() << "Encountered an address sanitizer error"; +} +void __tsan_on_report() { + FAIL() << "Encountered a thread sanitizer error"; +} +} // extern "C" +``` + +After compiling your project with one of the sanitizers enabled, if a particular +test triggers a sanitizer error, GoogleTest will report that it failed. diff --git a/Engine/lib/assimp/contrib/googletest/docs/assets/css/style.scss b/Engine/lib/assimp/contrib/googletest/docs/assets/css/style.scss new file mode 100644 index 000000000..bb30f418d --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/assets/css/style.scss @@ -0,0 +1,5 @@ +--- +--- + +@import "jekyll-theme-primer"; +@import "main"; diff --git a/Engine/lib/assimp/contrib/googletest/docs/community_created_documentation.md b/Engine/lib/assimp/contrib/googletest/docs/community_created_documentation.md new file mode 100644 index 000000000..4569075ff --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/community_created_documentation.md @@ -0,0 +1,7 @@ +# Community-Created Documentation + +The following is a list, in no particular order, of links to documentation +created by the Googletest community. + +* [Googlemock Insights](https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/googletest/insights.md), + by [ElectricRCAircraftGuy](https://github.com/ElectricRCAircraftGuy) diff --git a/Engine/lib/assimp/contrib/googletest/docs/faq.md b/Engine/lib/assimp/contrib/googletest/docs/faq.md new file mode 100644 index 000000000..192809729 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/faq.md @@ -0,0 +1,692 @@ +# GoogleTest FAQ + +## Why should test suite names and test names not contain underscore? + +{: .callout .note} +Note: GoogleTest reserves underscore (`_`) for special purpose keywords, such as +[the `DISABLED_` prefix](advanced.md#temporarily-disabling-tests), in addition +to the following rationale. + +Underscore (`_`) is special, as C++ reserves the following to be used by the +compiler and the standard library: + +1. any identifier that starts with an `_` followed by an upper-case letter, and +2. any identifier that contains two consecutive underscores (i.e. `__`) + *anywhere* in its name. + +User code is *prohibited* from using such identifiers. + +Now let's look at what this means for `TEST` and `TEST_F`. + +Currently `TEST(TestSuiteName, TestName)` generates a class named +`TestSuiteName_TestName_Test`. What happens if `TestSuiteName` or `TestName` +contains `_`? + +1. If `TestSuiteName` starts with an `_` followed by an upper-case letter (say, + `_Foo`), we end up with `_Foo_TestName_Test`, which is reserved and thus + invalid. +2. If `TestSuiteName` ends with an `_` (say, `Foo_`), we get + `Foo__TestName_Test`, which is invalid. +3. If `TestName` starts with an `_` (say, `_Bar`), we get + `TestSuiteName__Bar_Test`, which is invalid. +4. If `TestName` ends with an `_` (say, `Bar_`), we get + `TestSuiteName_Bar__Test`, which is invalid. + +So clearly `TestSuiteName` and `TestName` cannot start or end with `_` +(Actually, `TestSuiteName` can start with `_` -- as long as the `_` isn't +followed by an upper-case letter. But that's getting complicated. So for +simplicity we just say that it cannot start with `_`.). + +It may seem fine for `TestSuiteName` and `TestName` to contain `_` in the +middle. However, consider this: + +```c++ +TEST(Time, Flies_Like_An_Arrow) { ... } +TEST(Time_Flies, Like_An_Arrow) { ... } +``` + +Now, the two `TEST`s will both generate the same class +(`Time_Flies_Like_An_Arrow_Test`). That's not good. + +So for simplicity, we just ask the users to avoid `_` in `TestSuiteName` and +`TestName`. The rule is more constraining than necessary, but it's simple and +easy to remember. It also gives GoogleTest some wiggle room in case its +implementation needs to change in the future. + +If you violate the rule, there may not be immediate consequences, but your test +may (just may) break with a new compiler (or a new version of the compiler you +are using) or with a new version of GoogleTest. Therefore it's best to follow +the rule. + +## Why does GoogleTest support `EXPECT_EQ(NULL, ptr)` and `ASSERT_EQ(NULL, ptr)` but not `EXPECT_NE(NULL, ptr)` and `ASSERT_NE(NULL, ptr)`? + +First of all, you can use `nullptr` with each of these macros, e.g. +`EXPECT_EQ(ptr, nullptr)`, `EXPECT_NE(ptr, nullptr)`, `ASSERT_EQ(ptr, nullptr)`, +`ASSERT_NE(ptr, nullptr)`. This is the preferred syntax in the style guide +because `nullptr` does not have the type problems that `NULL` does. + +Due to some peculiarity of C++, it requires some non-trivial template meta +programming tricks to support using `NULL` as an argument of the `EXPECT_XX()` +and `ASSERT_XX()` macros. Therefore we only do it where it's most needed +(otherwise we make the implementation of GoogleTest harder to maintain and more +error-prone than necessary). + +Historically, the `EXPECT_EQ()` macro took the *expected* value as its first +argument and the *actual* value as the second, though this argument order is now +discouraged. It was reasonable that someone wanted +to write `EXPECT_EQ(NULL, some_expression)`, and this indeed was requested +several times. Therefore we implemented it. + +The need for `EXPECT_NE(NULL, ptr)` wasn't nearly as strong. When the assertion +fails, you already know that `ptr` must be `NULL`, so it doesn't add any +information to print `ptr` in this case. That means `EXPECT_TRUE(ptr != NULL)` +works just as well. + +If we were to support `EXPECT_NE(NULL, ptr)`, for consistency we'd have to +support `EXPECT_NE(ptr, NULL)` as well. This means using the template meta +programming tricks twice in the implementation, making it even harder to +understand and maintain. We believe the benefit doesn't justify the cost. + +Finally, with the growth of the gMock matcher library, we are encouraging people +to use the unified `EXPECT_THAT(value, matcher)` syntax more often in tests. One +significant advantage of the matcher approach is that matchers can be easily +combined to form new matchers, while the `EXPECT_NE`, etc, macros cannot be +easily combined. Therefore we want to invest more in the matchers than in the +`EXPECT_XX()` macros. + +## I need to test that different implementations of an interface satisfy some common requirements. Should I use typed tests or value-parameterized tests? + +For testing various implementations of the same interface, either typed tests or +value-parameterized tests can get it done. It's really up to you the user to +decide which is more convenient for you, depending on your particular case. Some +rough guidelines: + +* Typed tests can be easier to write if instances of the different + implementations can be created the same way, modulo the type. For example, + if all these implementations have a public default constructor (such that + you can write `new TypeParam`), or if their factory functions have the same + form (e.g. `CreateInstance()`). +* Value-parameterized tests can be easier to write if you need different code + patterns to create different implementations' instances, e.g. `new Foo` vs + `new Bar(5)`. To accommodate for the differences, you can write factory + function wrappers and pass these function pointers to the tests as their + parameters. +* When a typed test fails, the default output includes the name of the type, + which can help you quickly identify which implementation is wrong. + Value-parameterized tests only show the number of the failed iteration by + default. You will need to define a function that returns the iteration name + and pass it as the third parameter to INSTANTIATE_TEST_SUITE_P to have more + useful output. +* When using typed tests, you need to make sure you are testing against the + interface type, not the concrete types (in other words, you want to make + sure `implicit_cast(my_concrete_impl)` works, not just that + `my_concrete_impl` works). It's less likely to make mistakes in this area + when using value-parameterized tests. + +I hope I didn't confuse you more. :-) If you don't mind, I'd suggest you to give +both approaches a try. Practice is a much better way to grasp the subtle +differences between the two tools. Once you have some concrete experience, you +can much more easily decide which one to use the next time. + +## I got some run-time errors about invalid proto descriptors when using `ProtocolMessageEquals`. Help! + +{: .callout .note} +**Note:** `ProtocolMessageEquals` and `ProtocolMessageEquiv` are *deprecated* +now. Please use `EqualsProto`, etc instead. + +`ProtocolMessageEquals` and `ProtocolMessageEquiv` were redefined recently and +are now less tolerant of invalid protocol buffer definitions. In particular, if +you have a `foo.proto` that doesn't fully qualify the type of a protocol message +it references (e.g. `message` where it should be `message`), you +will now get run-time errors like: + +``` +... descriptor.cc:...] Invalid proto descriptor for file "path/to/foo.proto": +... descriptor.cc:...] blah.MyMessage.my_field: ".Bar" is not defined. +``` + +If you see this, your `.proto` file is broken and needs to be fixed by making +the types fully qualified. The new definition of `ProtocolMessageEquals` and +`ProtocolMessageEquiv` just happen to reveal your bug. + +## My death test modifies some state, but the change seems lost after the death test finishes. Why? + +Death tests (`EXPECT_DEATH`, etc) are executed in a sub-process s.t. the +expected crash won't kill the test program (i.e. the parent process). As a +result, any in-memory side effects they incur are observable in their respective +sub-processes, but not in the parent process. You can think of them as running +in a parallel universe, more or less. + +In particular, if you use mocking and the death test statement invokes some mock +methods, the parent process will think the calls have never occurred. Therefore, +you may want to move your `EXPECT_CALL` statements inside the `EXPECT_DEATH` +macro. + +## EXPECT_EQ(htonl(blah), blah_blah) generates weird compiler errors in opt mode. Is this a GoogleTest bug? + +Actually, the bug is in `htonl()`. + +According to `'man htonl'`, `htonl()` is a *function*, which means it's valid to +use `htonl` as a function pointer. However, in opt mode `htonl()` is defined as +a *macro*, which breaks this usage. + +Worse, the macro definition of `htonl()` uses a `gcc` extension and is *not* +standard C++. That hacky implementation has some ad hoc limitations. In +particular, it prevents you from writing `Foo()`, where `Foo` +is a template that has an integral argument. + +The implementation of `EXPECT_EQ(a, b)` uses `sizeof(... a ...)` inside a +template argument, and thus doesn't compile in opt mode when `a` contains a call +to `htonl()`. It is difficult to make `EXPECT_EQ` bypass the `htonl()` bug, as +the solution must work with different compilers on various platforms. + +## The compiler complains about "undefined references" to some static const member variables, but I did define them in the class body. What's wrong? + +If your class has a static data member: + +```c++ +// foo.h +class Foo { + ... + static const int kBar = 100; +}; +``` + +You also need to define it *outside* of the class body in `foo.cc`: + +```c++ +const int Foo::kBar; // No initializer here. +``` + +Otherwise your code is **invalid C++**, and may break in unexpected ways. In +particular, using it in GoogleTest comparison assertions (`EXPECT_EQ`, etc) will +generate an "undefined reference" linker error. The fact that "it used to work" +doesn't mean it's valid. It just means that you were lucky. :-) + +If the declaration of the static data member is `constexpr` then it is +implicitly an `inline` definition, and a separate definition in `foo.cc` is not +needed: + +```c++ +// foo.h +class Foo { + ... + static constexpr int kBar = 100; // Defines kBar, no need to do it in foo.cc. +}; +``` + +## Can I derive a test fixture from another? + +Yes. + +Each test fixture has a corresponding and same named test suite. This means only +one test suite can use a particular fixture. Sometimes, however, multiple test +cases may want to use the same or slightly different fixtures. For example, you +may want to make sure that all of a GUI library's test suites don't leak +important system resources like fonts and brushes. + +In GoogleTest, you share a fixture among test suites by putting the shared logic +in a base test fixture, then deriving from that base a separate fixture for each +test suite that wants to use this common logic. You then use `TEST_F()` to write +tests using each derived fixture. + +Typically, your code looks like this: + +```c++ +// Defines a base test fixture. +class BaseTest : public ::testing::Test { + protected: + ... +}; + +// Derives a fixture FooTest from BaseTest. +class FooTest : public BaseTest { + protected: + void SetUp() override { + BaseTest::SetUp(); // Sets up the base fixture first. + ... additional set-up work ... + } + + void TearDown() override { + ... clean-up work for FooTest ... + BaseTest::TearDown(); // Remember to tear down the base fixture + // after cleaning up FooTest! + } + + ... functions and variables for FooTest ... +}; + +// Tests that use the fixture FooTest. +TEST_F(FooTest, Bar) { ... } +TEST_F(FooTest, Baz) { ... } + +... additional fixtures derived from BaseTest ... +``` + +If necessary, you can continue to derive test fixtures from a derived fixture. +GoogleTest has no limit on how deep the hierarchy can be. + +For a complete example using derived test fixtures, see +[sample5_unittest.cc](https://github.com/google/googletest/blob/main/googletest/samples/sample5_unittest.cc). + +## My compiler complains "void value not ignored as it ought to be." What does this mean? + +You're probably using an `ASSERT_*()` in a function that doesn't return `void`. +`ASSERT_*()` can only be used in `void` functions, due to exceptions being +disabled by our build system. Please see more details +[here](advanced.md#assertion-placement). + +## My death test hangs (or seg-faults). How do I fix it? + +In GoogleTest, death tests are run in a child process and the way they work is +delicate. To write death tests you really need to understand how they work—see +the details at [Death Assertions](reference/assertions.md#death) in the +Assertions Reference. + +In particular, death tests don't like having multiple threads in the parent +process. So the first thing you can try is to eliminate creating threads outside +of `EXPECT_DEATH()`. For example, you may want to use mocks or fake objects +instead of real ones in your tests. + +Sometimes this is impossible as some library you must use may be creating +threads before `main()` is even reached. In this case, you can try to minimize +the chance of conflicts by either moving as many activities as possible inside +`EXPECT_DEATH()` (in the extreme case, you want to move everything inside), or +leaving as few things as possible in it. Also, you can try to set the death test +style to `"threadsafe"`, which is safer but slower, and see if it helps. + +If you go with thread-safe death tests, remember that they rerun the test +program from the beginning in the child process. Therefore make sure your +program can run side-by-side with itself and is deterministic. + +In the end, this boils down to good concurrent programming. You have to make +sure that there are no race conditions or deadlocks in your program. No silver +bullet - sorry! + +## Should I use the constructor/destructor of the test fixture or SetUp()/TearDown()? {#CtorVsSetUp} + +The first thing to remember is that GoogleTest does **not** reuse the same test +fixture object across multiple tests. For each `TEST_F`, GoogleTest will create +a **fresh** test fixture object, immediately call `SetUp()`, run the test body, +call `TearDown()`, and then delete the test fixture object. + +When you need to write per-test set-up and tear-down logic, you have the choice +between using the test fixture constructor/destructor or `SetUp()/TearDown()`. +The former is usually preferred, as it has the following benefits: + +* By initializing a member variable in the constructor, we have the option to + make it `const`, which helps prevent accidental changes to its value and + makes the tests more obviously correct. +* In case we need to subclass the test fixture class, the subclass' + constructor is guaranteed to call the base class' constructor *first*, and + the subclass' destructor is guaranteed to call the base class' destructor + *afterward*. With `SetUp()/TearDown()`, a subclass may make the mistake of + forgetting to call the base class' `SetUp()/TearDown()` or call them at the + wrong time. + +You may still want to use `SetUp()/TearDown()` in the following cases: + +* C++ does not allow virtual function calls in constructors and destructors. + You can call a method declared as virtual, but it will not use dynamic + dispatch. It will use the definition from the class the constructor of which + is currently executing. This is because calling a virtual method before the + derived class constructor has a chance to run is very dangerous - the + virtual method might operate on uninitialized data. Therefore, if you need + to call a method that will be overridden in a derived class, you have to use + `SetUp()/TearDown()`. +* In the body of a constructor (or destructor), it's not possible to use the + `ASSERT_xx` macros. Therefore, if the set-up operation could cause a fatal + test failure that should prevent the test from running, it's necessary to + use `abort` and abort the whole test + executable, or to use `SetUp()` instead of a constructor. +* If the tear-down operation could throw an exception, you must use + `TearDown()` as opposed to the destructor, as throwing in a destructor leads + to undefined behavior and usually will kill your program right away. Note + that many standard libraries (like STL) may throw when exceptions are + enabled in the compiler. Therefore you should prefer `TearDown()` if you + want to write portable tests that work with or without exceptions. +* The GoogleTest team is considering making the assertion macros throw on + platforms where exceptions are enabled (e.g. Windows, Mac OS, and Linux + client-side), which will eliminate the need for the user to propagate + failures from a subroutine to its caller. Therefore, you shouldn't use + GoogleTest assertions in a destructor if your code could run on such a + platform. + +## The compiler complains "no matching function to call" when I use ASSERT_PRED*. How do I fix it? + +See details for [`EXPECT_PRED*`](reference/assertions.md#EXPECT_PRED) in the +Assertions Reference. + +## My compiler complains about "ignoring return value" when I call RUN_ALL_TESTS(). Why? + +Some people had been ignoring the return value of `RUN_ALL_TESTS()`. That is, +instead of + +```c++ + return RUN_ALL_TESTS(); +``` + +they write + +```c++ + RUN_ALL_TESTS(); +``` + +This is **wrong and dangerous**. The testing services needs to see the return +value of `RUN_ALL_TESTS()` in order to determine if a test has passed. If your +`main()` function ignores it, your test will be considered successful even if it +has a GoogleTest assertion failure. Very bad. + +We have decided to fix this (thanks to Michael Chastain for the idea). Now, your +code will no longer be able to ignore `RUN_ALL_TESTS()` when compiled with +`gcc`. If you do so, you'll get a compiler error. + +If you see the compiler complaining about you ignoring the return value of +`RUN_ALL_TESTS()`, the fix is simple: just make sure its value is used as the +return value of `main()`. + +But how could we introduce a change that breaks existing tests? Well, in this +case, the code was already broken in the first place, so we didn't break it. :-) + +## My compiler complains that a constructor (or destructor) cannot return a value. What's going on? + +Due to a peculiarity of C++, in order to support the syntax for streaming +messages to an `ASSERT_*`, e.g. + +```c++ + ASSERT_EQ(1, Foo()) << "blah blah" << foo; +``` + +we had to give up using `ASSERT*` and `FAIL*` (but not `EXPECT*` and +`ADD_FAILURE*`) in constructors and destructors. The workaround is to move the +content of your constructor/destructor to a private void member function, or +switch to `EXPECT_*()` if that works. This +[section](advanced.md#assertion-placement) in the user's guide explains it. + +## My SetUp() function is not called. Why? + +C++ is case-sensitive. Did you spell it as `Setup()`? + +Similarly, sometimes people spell `SetUpTestSuite()` as `SetupTestSuite()` and +wonder why it's never called. + +## I have several test suites which share the same test fixture logic, do I have to define a new test fixture class for each of them? This seems pretty tedious. + +You don't have to. Instead of + +```c++ +class FooTest : public BaseTest {}; + +TEST_F(FooTest, Abc) { ... } +TEST_F(FooTest, Def) { ... } + +class BarTest : public BaseTest {}; + +TEST_F(BarTest, Abc) { ... } +TEST_F(BarTest, Def) { ... } +``` + +you can simply `typedef` the test fixtures: + +```c++ +typedef BaseTest FooTest; + +TEST_F(FooTest, Abc) { ... } +TEST_F(FooTest, Def) { ... } + +typedef BaseTest BarTest; + +TEST_F(BarTest, Abc) { ... } +TEST_F(BarTest, Def) { ... } +``` + +## GoogleTest output is buried in a whole bunch of LOG messages. What do I do? + +The GoogleTest output is meant to be a concise and human-friendly report. If +your test generates textual output itself, it will mix with the GoogleTest +output, making it hard to read. However, there is an easy solution to this +problem. + +Since `LOG` messages go to stderr, we decided to let GoogleTest output go to +stdout. This way, you can easily separate the two using redirection. For +example: + +```shell +$ ./my_test > gtest_output.txt +``` + +## Why should I prefer test fixtures over global variables? + +There are several good reasons: + +1. It's likely your test needs to change the states of its global variables. + This makes it difficult to keep side effects from escaping one test and + contaminating others, making debugging difficult. By using fixtures, each + test has a fresh set of variables that's different (but with the same + names). Thus, tests are kept independent of each other. +2. Global variables pollute the global namespace. +3. Test fixtures can be reused via subclassing, which cannot be done easily + with global variables. This is useful if many test suites have something in + common. + +## What can the statement argument in ASSERT_DEATH() be? + +`ASSERT_DEATH(statement, matcher)` (or any death assertion macro) can be used +wherever *`statement`* is valid. So basically *`statement`* can be any C++ +statement that makes sense in the current context. In particular, it can +reference global and/or local variables, and can be: + +* a simple function call (often the case), +* a complex expression, or +* a compound statement. + +Some examples are shown here: + +```c++ +// A death test can be a simple function call. +TEST(MyDeathTest, FunctionCall) { + ASSERT_DEATH(Xyz(5), "Xyz failed"); +} + +// Or a complex expression that references variables and functions. +TEST(MyDeathTest, ComplexExpression) { + const bool c = Condition(); + ASSERT_DEATH((c ? Func1(0) : object2.Method("test")), + "(Func1|Method) failed"); +} + +// Death assertions can be used anywhere in a function. In +// particular, they can be inside a loop. +TEST(MyDeathTest, InsideLoop) { + // Verifies that Foo(0), Foo(1), ..., and Foo(4) all die. + for (int i = 0; i < 5; i++) { + EXPECT_DEATH_M(Foo(i), "Foo has \\d+ errors", + ::testing::Message() << "where i is " << i); + } +} + +// A death assertion can contain a compound statement. +TEST(MyDeathTest, CompoundStatement) { + // Verifies that at lease one of Bar(0), Bar(1), ..., and + // Bar(4) dies. + ASSERT_DEATH({ + for (int i = 0; i < 5; i++) { + Bar(i); + } + }, + "Bar has \\d+ errors"); +} +``` + +## I have a fixture class `FooTest`, but `TEST_F(FooTest, Bar)` gives me error ``"no matching function for call to `FooTest::FooTest()'"``. Why? + +GoogleTest needs to be able to create objects of your test fixture class, so it +must have a default constructor. Normally the compiler will define one for you. +However, there are cases where you have to define your own: + +* If you explicitly declare a non-default constructor for class `FooTest` + (`DISALLOW_EVIL_CONSTRUCTORS()` does this), then you need to define a + default constructor, even if it would be empty. +* If `FooTest` has a const non-static data member, then you have to define the + default constructor *and* initialize the const member in the initializer + list of the constructor. (Early versions of `gcc` doesn't force you to + initialize the const member. It's a bug that has been fixed in `gcc 4`.) + +## Why does ASSERT_DEATH complain about previous threads that were already joined? + +With the Linux pthread library, there is no turning back once you cross the line +from a single thread to multiple threads. The first time you create a thread, a +manager thread is created in addition, so you get 3, not 2, threads. Later when +the thread you create joins the main thread, the thread count decrements by 1, +but the manager thread will never be killed, so you still have 2 threads, which +means you cannot safely run a death test. + +The new NPTL thread library doesn't suffer from this problem, as it doesn't +create a manager thread. However, if you don't control which machine your test +runs on, you shouldn't depend on this. + +## Why does GoogleTest require the entire test suite, instead of individual tests, to be named *DeathTest when it uses ASSERT_DEATH? + +GoogleTest does not interleave tests from different test suites. That is, it +runs all tests in one test suite first, and then runs all tests in the next test +suite, and so on. GoogleTest does this because it needs to set up a test suite +before the first test in it is run, and tear it down afterwards. Splitting up +the test case would require multiple set-up and tear-down processes, which is +inefficient and makes the semantics unclean. + +If we were to determine the order of tests based on test name instead of test +case name, then we would have a problem with the following situation: + +```c++ +TEST_F(FooTest, AbcDeathTest) { ... } +TEST_F(FooTest, Uvw) { ... } + +TEST_F(BarTest, DefDeathTest) { ... } +TEST_F(BarTest, Xyz) { ... } +``` + +Since `FooTest.AbcDeathTest` needs to run before `BarTest.Xyz`, and we don't +interleave tests from different test suites, we need to run all tests in the +`FooTest` case before running any test in the `BarTest` case. This contradicts +with the requirement to run `BarTest.DefDeathTest` before `FooTest.Uvw`. + +## But I don't like calling my entire test suite \*DeathTest when it contains both death tests and non-death tests. What do I do? + +You don't have to, but if you like, you may split up the test suite into +`FooTest` and `FooDeathTest`, where the names make it clear that they are +related: + +```c++ +class FooTest : public ::testing::Test { ... }; + +TEST_F(FooTest, Abc) { ... } +TEST_F(FooTest, Def) { ... } + +using FooDeathTest = FooTest; + +TEST_F(FooDeathTest, Uvw) { ... EXPECT_DEATH(...) ... } +TEST_F(FooDeathTest, Xyz) { ... ASSERT_DEATH(...) ... } +``` + +## GoogleTest prints the LOG messages in a death test's child process only when the test fails. How can I see the LOG messages when the death test succeeds? + +Printing the LOG messages generated by the statement inside `EXPECT_DEATH()` +makes it harder to search for real problems in the parent's log. Therefore, +GoogleTest only prints them when the death test has failed. + +If you really need to see such LOG messages, a workaround is to temporarily +break the death test (e.g. by changing the regex pattern it is expected to +match). Admittedly, this is a hack. We'll consider a more permanent solution +after the fork-and-exec-style death tests are implemented. + +## The compiler complains about `no match for 'operator<<'` when I use an assertion. What gives? + +If you use a user-defined type `FooType` in an assertion, you must make sure +there is an `std::ostream& operator<<(std::ostream&, const FooType&)` function +defined such that we can print a value of `FooType`. + +In addition, if `FooType` is declared in a name space, the `<<` operator also +needs to be defined in the *same* name space. See +[Tip of the Week #49](http://abseil.io/tips/49) for details. + +## How do I suppress the memory leak messages on Windows? + +Since the statically initialized GoogleTest singleton requires allocations on +the heap, the Visual C++ memory leak detector will report memory leaks at the +end of the program run. The easiest way to avoid this is to use the +`_CrtMemCheckpoint` and `_CrtMemDumpAllObjectsSince` calls to not report any +statically initialized heap objects. See MSDN for more details and additional +heap check/debug routines. + +## How can my code detect if it is running in a test? + +If you write code that sniffs whether it's running in a test and does different +things accordingly, you are leaking test-only logic into production code and +there is no easy way to ensure that the test-only code paths aren't run by +mistake in production. Such cleverness also leads to +[Heisenbugs](https://en.wikipedia.org/wiki/Heisenbug). Therefore we strongly +advise against the practice, and GoogleTest doesn't provide a way to do it. + +In general, the recommended way to cause the code to behave differently under +test is [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection). You can inject +different functionality from the test and from the production code. Since your +production code doesn't link in the for-test logic at all (the +[`testonly`](http://docs.bazel.build/versions/master/be/common-definitions.html#common.testonly) attribute for BUILD targets helps to ensure +that), there is no danger in accidentally running it. + +However, if you *really*, *really*, *really* have no choice, and if you follow +the rule of ending your test program names with `_test`, you can use the +*horrible* hack of sniffing your executable name (`argv[0]` in `main()`) to know +whether the code is under test. + +## How do I temporarily disable a test? + +If you have a broken test that you cannot fix right away, you can add the +`DISABLED_` prefix to its name. This will exclude it from execution. This is +better than commenting out the code or using `#if 0`, as disabled tests are +still compiled (and thus won't rot). + +To include disabled tests in test execution, just invoke the test program with +the `--gtest_also_run_disabled_tests` flag. + +## Is it OK if I have two separate `TEST(Foo, Bar)` test methods defined in different namespaces? + +Yes. + +The rule is **all test methods in the same test suite must use the same fixture +class.** This means that the following is **allowed** because both tests use the +same fixture class (`::testing::Test`). + +```c++ +namespace foo { +TEST(CoolTest, DoSomething) { + SUCCEED(); +} +} // namespace foo + +namespace bar { +TEST(CoolTest, DoSomething) { + SUCCEED(); +} +} // namespace bar +``` + +However, the following code is **not allowed** and will produce a runtime error +from GoogleTest because the test methods are using different test fixture +classes with the same test suite name. + +```c++ +namespace foo { +class CoolTest : public ::testing::Test {}; // Fixture foo::CoolTest +TEST_F(CoolTest, DoSomething) { + SUCCEED(); +} +} // namespace foo + +namespace bar { +class CoolTest : public ::testing::Test {}; // Fixture: bar::CoolTest +TEST_F(CoolTest, DoSomething) { + SUCCEED(); +} +} // namespace bar +``` diff --git a/Engine/lib/assimp/contrib/googletest/docs/gmock_cheat_sheet.md b/Engine/lib/assimp/contrib/googletest/docs/gmock_cheat_sheet.md new file mode 100644 index 000000000..ddafaaa22 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/gmock_cheat_sheet.md @@ -0,0 +1,241 @@ +# gMock Cheat Sheet + +## Defining a Mock Class + +### Mocking a Normal Class {#MockClass} + +Given + +```cpp +class Foo { + public: + virtual ~Foo(); + virtual int GetSize() const = 0; + virtual string Describe(const char* name) = 0; + virtual string Describe(int type) = 0; + virtual bool Process(Bar elem, int count) = 0; +}; +``` + +(note that `~Foo()` **must** be virtual) we can define its mock as + +```cpp +#include + +class MockFoo : public Foo { + public: + MOCK_METHOD(int, GetSize, (), (const, override)); + MOCK_METHOD(string, Describe, (const char* name), (override)); + MOCK_METHOD(string, Describe, (int type), (override)); + MOCK_METHOD(bool, Process, (Bar elem, int count), (override)); +}; +``` + +To create a "nice" mock, which ignores all uninteresting calls, a "naggy" mock, +which warns on all uninteresting calls, or a "strict" mock, which treats them as +failures: + +```cpp +using ::testing::NiceMock; +using ::testing::NaggyMock; +using ::testing::StrictMock; + +NiceMock nice_foo; // The type is a subclass of MockFoo. +NaggyMock naggy_foo; // The type is a subclass of MockFoo. +StrictMock strict_foo; // The type is a subclass of MockFoo. +``` + +{: .callout .note} +**Note:** A mock object is currently naggy by default. We may make it nice by +default in the future. + +### Mocking a Class Template {#MockTemplate} + +Class templates can be mocked just like any class. + +To mock + +```cpp +template +class StackInterface { + public: + virtual ~StackInterface(); + virtual int GetSize() const = 0; + virtual void Push(const Elem& x) = 0; +}; +``` + +(note that all member functions that are mocked, including `~StackInterface()` +**must** be virtual). + +```cpp +template +class MockStack : public StackInterface { + public: + MOCK_METHOD(int, GetSize, (), (const, override)); + MOCK_METHOD(void, Push, (const Elem& x), (override)); +}; +``` + +### Specifying Calling Conventions for Mock Functions + +If your mock function doesn't use the default calling convention, you can +specify it by adding `Calltype(convention)` to `MOCK_METHOD`'s 4th parameter. +For example, + +```cpp + MOCK_METHOD(bool, Foo, (int n), (Calltype(STDMETHODCALLTYPE))); + MOCK_METHOD(int, Bar, (double x, double y), + (const, Calltype(STDMETHODCALLTYPE))); +``` + +where `STDMETHODCALLTYPE` is defined by `` on Windows. + +## Using Mocks in Tests {#UsingMocks} + +The typical work flow is: + +1. Import the gMock names you need to use. All gMock symbols are in the + `testing` namespace unless they are macros or otherwise noted. +2. Create the mock objects. +3. Optionally, set the default actions of the mock objects. +4. Set your expectations on the mock objects (How will they be called? What + will they do?). +5. Exercise code that uses the mock objects; if necessary, check the result + using googletest assertions. +6. When a mock object is destructed, gMock automatically verifies that all + expectations on it have been satisfied. + +Here's an example: + +```cpp +using ::testing::Return; // #1 + +TEST(BarTest, DoesThis) { + MockFoo foo; // #2 + + ON_CALL(foo, GetSize()) // #3 + .WillByDefault(Return(1)); + // ... other default actions ... + + EXPECT_CALL(foo, Describe(5)) // #4 + .Times(3) + .WillRepeatedly(Return("Category 5")); + // ... other expectations ... + + EXPECT_EQ(MyProductionFunction(&foo), "good"); // #5 +} // #6 +``` + +## Setting Default Actions {#OnCall} + +gMock has a **built-in default action** for any function that returns `void`, +`bool`, a numeric value, or a pointer. In C++11, it will additionally returns +the default-constructed value, if one exists for the given type. + +To customize the default action for functions with return type `T`, use +[`DefaultValue`](reference/mocking.md#DefaultValue). For example: + +```cpp + // Sets the default action for return type std::unique_ptr to + // creating a new Buzz every time. + DefaultValue>::SetFactory( + [] { return std::make_unique(AccessLevel::kInternal); }); + + // When this fires, the default action of MakeBuzz() will run, which + // will return a new Buzz object. + EXPECT_CALL(mock_buzzer_, MakeBuzz("hello")).Times(AnyNumber()); + + auto buzz1 = mock_buzzer_.MakeBuzz("hello"); + auto buzz2 = mock_buzzer_.MakeBuzz("hello"); + EXPECT_NE(buzz1, nullptr); + EXPECT_NE(buzz2, nullptr); + EXPECT_NE(buzz1, buzz2); + + // Resets the default action for return type std::unique_ptr, + // to avoid interfere with other tests. + DefaultValue>::Clear(); +``` + +To customize the default action for a particular method of a specific mock +object, use [`ON_CALL`](reference/mocking.md#ON_CALL). `ON_CALL` has a similar +syntax to `EXPECT_CALL`, but it is used for setting default behaviors when you +do not require that the mock method is called. See +[Knowing When to Expect](gmock_cook_book.md#UseOnCall) for a more detailed +discussion. + +## Setting Expectations {#ExpectCall} + +See [`EXPECT_CALL`](reference/mocking.md#EXPECT_CALL) in the Mocking Reference. + +## Matchers {#MatcherList} + +See the [Matchers Reference](reference/matchers.md). + +## Actions {#ActionList} + +See the [Actions Reference](reference/actions.md). + +## Cardinalities {#CardinalityList} + +See the [`Times` clause](reference/mocking.md#EXPECT_CALL.Times) of +`EXPECT_CALL` in the Mocking Reference. + +## Expectation Order + +By default, expectations can be matched in *any* order. If some or all +expectations must be matched in a given order, you can use the +[`After` clause](reference/mocking.md#EXPECT_CALL.After) or +[`InSequence` clause](reference/mocking.md#EXPECT_CALL.InSequence) of +`EXPECT_CALL`, or use an [`InSequence` object](reference/mocking.md#InSequence). + +## Verifying and Resetting a Mock + +gMock will verify the expectations on a mock object when it is destructed, or +you can do it earlier: + +```cpp +using ::testing::Mock; +... +// Verifies and removes the expectations on mock_obj; +// returns true if and only if successful. +Mock::VerifyAndClearExpectations(&mock_obj); +... +// Verifies and removes the expectations on mock_obj; +// also removes the default actions set by ON_CALL(); +// returns true if and only if successful. +Mock::VerifyAndClear(&mock_obj); +``` + +Do not set new expectations after verifying and clearing a mock after its use. +Setting expectations after code that exercises the mock has undefined behavior. +See [Using Mocks in Tests](gmock_for_dummies.md#using-mocks-in-tests) for more +information. + +You can also tell gMock that a mock object can be leaked and doesn't need to be +verified: + +```cpp +Mock::AllowLeak(&mock_obj); +``` + +## Mock Classes + +gMock defines a convenient mock class template + +```cpp +class MockFunction { + public: + MOCK_METHOD(R, Call, (A1, ..., An)); +}; +``` + +See this [recipe](gmock_cook_book.md#UsingCheckPoints) for one application of +it. + +## Flags + +| Flag | Description | +| :----------------------------- | :---------------------------------------- | +| `--gmock_catch_leaked_mocks=0` | Don't report leaked mock objects as failures. | +| `--gmock_verbose=LEVEL` | Sets the default verbosity level (`info`, `warning`, or `error`) of Google Mock messages. | diff --git a/Engine/lib/assimp/contrib/googletest/docs/gmock_cook_book.md b/Engine/lib/assimp/contrib/googletest/docs/gmock_cook_book.md new file mode 100644 index 000000000..da10918c9 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/gmock_cook_book.md @@ -0,0 +1,4344 @@ +# gMock Cookbook + +You can find recipes for using gMock here. If you haven't yet, please read +[the dummy guide](gmock_for_dummies.md) first to make sure you understand the +basics. + +{: .callout .note} +**Note:** gMock lives in the `testing` name space. For readability, it is +recommended to write `using ::testing::Foo;` once in your file before using the +name `Foo` defined by gMock. We omit such `using` statements in this section for +brevity, but you should do it in your own code. + +## Creating Mock Classes + +Mock classes are defined as normal classes, using the `MOCK_METHOD` macro to +generate mocked methods. The macro gets 3 or 4 parameters: + +```cpp +class MyMock { + public: + MOCK_METHOD(ReturnType, MethodName, (Args...)); + MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...)); +}; +``` + +The first 3 parameters are simply the method declaration, split into 3 parts. +The 4th parameter accepts a closed list of qualifiers, which affect the +generated method: + +* **`const`** - Makes the mocked method a `const` method. Required if + overriding a `const` method. +* **`override`** - Marks the method with `override`. Recommended if overriding + a `virtual` method. +* **`noexcept`** - Marks the method with `noexcept`. Required if overriding a + `noexcept` method. +* **`Calltype(...)`** - Sets the call type for the method (e.g. to + `STDMETHODCALLTYPE`), useful in Windows. +* **`ref(...)`** - Marks the method with the reference qualification + specified. Required if overriding a method that has reference + qualifications. Eg `ref(&)` or `ref(&&)`. + +### Dealing with unprotected commas + +Unprotected commas, i.e. commas which are not surrounded by parentheses, prevent +`MOCK_METHOD` from parsing its arguments correctly: + +{: .bad} +```cpp +class MockFoo { + public: + MOCK_METHOD(std::pair, GetPair, ()); // Won't compile! + MOCK_METHOD(bool, CheckMap, (std::map, bool)); // Won't compile! +}; +``` + +Solution 1 - wrap with parentheses: + +{: .good} +```cpp +class MockFoo { + public: + MOCK_METHOD((std::pair), GetPair, ()); + MOCK_METHOD(bool, CheckMap, ((std::map), bool)); +}; +``` + +Note that wrapping a return or argument type with parentheses is, in general, +invalid C++. `MOCK_METHOD` removes the parentheses. + +Solution 2 - define an alias: + +{: .good} +```cpp +class MockFoo { + public: + using BoolAndInt = std::pair; + MOCK_METHOD(BoolAndInt, GetPair, ()); + using MapIntDouble = std::map; + MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool)); +}; +``` + +### Mocking Private or Protected Methods + +You must always put a mock method definition (`MOCK_METHOD`) in a `public:` +section of the mock class, regardless of the method being mocked being `public`, +`protected`, or `private` in the base class. This allows `ON_CALL` and +`EXPECT_CALL` to reference the mock function from outside of the mock class. +(Yes, C++ allows a subclass to change the access level of a virtual function in +the base class.) Example: + +```cpp +class Foo { + public: + ... + virtual bool Transform(Gadget* g) = 0; + + protected: + virtual void Resume(); + + private: + virtual int GetTimeOut(); +}; + +class MockFoo : public Foo { + public: + ... + MOCK_METHOD(bool, Transform, (Gadget* g), (override)); + + // The following must be in the public section, even though the + // methods are protected or private in the base class. + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(int, GetTimeOut, (), (override)); +}; +``` + +### Mocking Overloaded Methods + +You can mock overloaded functions as usual. No special attention is required: + +```cpp +class Foo { + ... + + // Must be virtual as we'll inherit from Foo. + virtual ~Foo(); + + // Overloaded on the types and/or numbers of arguments. + virtual int Add(Element x); + virtual int Add(int times, Element x); + + // Overloaded on the const-ness of this object. + virtual Bar& GetBar(); + virtual const Bar& GetBar() const; +}; + +class MockFoo : public Foo { + ... + MOCK_METHOD(int, Add, (Element x), (override)); + MOCK_METHOD(int, Add, (int times, Element x), (override)); + + MOCK_METHOD(Bar&, GetBar, (), (override)); + MOCK_METHOD(const Bar&, GetBar, (), (const, override)); +}; +``` + +{: .callout .note} +**Note:** if you don't mock all versions of the overloaded method, the compiler +will give you a warning about some methods in the base class being hidden. To +fix that, use `using` to bring them in scope: + +```cpp +class MockFoo : public Foo { + ... + using Foo::Add; + MOCK_METHOD(int, Add, (Element x), (override)); + // We don't want to mock int Add(int times, Element x); + ... +}; +``` + +### Mocking Class Templates + +You can mock class templates just like any class. + +```cpp +template +class StackInterface { + ... + // Must be virtual as we'll inherit from StackInterface. + virtual ~StackInterface(); + + virtual int GetSize() const = 0; + virtual void Push(const Elem& x) = 0; +}; + +template +class MockStack : public StackInterface { + ... + MOCK_METHOD(int, GetSize, (), (override)); + MOCK_METHOD(void, Push, (const Elem& x), (override)); +}; +``` + +### Mocking Non-virtual Methods {#MockingNonVirtualMethods} + +gMock can mock non-virtual functions to be used in Hi-perf dependency injection. + +In this case, instead of sharing a common base class with the real class, your +mock class will be *unrelated* to the real class, but contain methods with the +same signatures. The syntax for mocking non-virtual methods is the *same* as +mocking virtual methods (just don't add `override`): + +```cpp +// A simple packet stream class. None of its members is virtual. +class ConcretePacketStream { + public: + void AppendPacket(Packet* new_packet); + const Packet* GetPacket(size_t packet_number) const; + size_t NumberOfPackets() const; + ... +}; + +// A mock packet stream class. It inherits from no other, but defines +// GetPacket() and NumberOfPackets(). +class MockPacketStream { + public: + MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const)); + MOCK_METHOD(size_t, NumberOfPackets, (), (const)); + ... +}; +``` + +Note that the mock class doesn't define `AppendPacket()`, unlike the real class. +That's fine as long as the test doesn't need to call it. + +Next, you need a way to say that you want to use `ConcretePacketStream` in +production code, and use `MockPacketStream` in tests. Since the functions are +not virtual and the two classes are unrelated, you must specify your choice at +*compile time* (as opposed to run time). + +One way to do it is to templatize your code that needs to use a packet stream. +More specifically, you will give your code a template type argument for the type +of the packet stream. In production, you will instantiate your template with +`ConcretePacketStream` as the type argument. In tests, you will instantiate the +same template with `MockPacketStream`. For example, you may write: + +```cpp +template +void CreateConnection(PacketStream* stream) { ... } + +template +class PacketReader { + public: + void ReadPackets(PacketStream* stream, size_t packet_num); +}; +``` + +Then you can use `CreateConnection()` and +`PacketReader` in production code, and use +`CreateConnection()` and `PacketReader` in +tests. + +```cpp + MockPacketStream mock_stream; + EXPECT_CALL(mock_stream, ...)...; + .. set more expectations on mock_stream ... + PacketReader reader(&mock_stream); + ... exercise reader ... +``` + +### Mocking Free Functions + +It is not possible to directly mock a free function (i.e. a C-style function or +a static method). If you need to, you can rewrite your code to use an interface +(abstract class). + +Instead of calling a free function (say, `OpenFile`) directly, introduce an +interface for it and have a concrete subclass that calls the free function: + +```cpp +class FileInterface { + public: + ... + virtual bool Open(const char* path, const char* mode) = 0; +}; + +class File : public FileInterface { + public: + ... + bool Open(const char* path, const char* mode) override { + return OpenFile(path, mode); + } +}; +``` + +Your code should talk to `FileInterface` to open a file. Now it's easy to mock +out the function. + +This may seem like a lot of hassle, but in practice you often have multiple +related functions that you can put in the same interface, so the per-function +syntactic overhead will be much lower. + +If you are concerned about the performance overhead incurred by virtual +functions, and profiling confirms your concern, you can combine this with the +recipe for [mocking non-virtual methods](#MockingNonVirtualMethods). + +Alternatively, instead of introducing a new interface, you can rewrite your code +to accept a std::function instead of the free function, and then use +[MockFunction](#MockFunction) to mock the std::function. + +### Old-Style `MOCK_METHODn` Macros + +Before the generic `MOCK_METHOD` macro +[was introduced in 2018](https://github.com/google/googletest/commit/c5f08bf91944ce1b19bcf414fa1760e69d20afc2), +mocks where created using a family of macros collectively called `MOCK_METHODn`. +These macros are still supported, though migration to the new `MOCK_METHOD` is +recommended. + +The macros in the `MOCK_METHODn` family differ from `MOCK_METHOD`: + +* The general structure is `MOCK_METHODn(MethodName, ReturnType(Args))`, + instead of `MOCK_METHOD(ReturnType, MethodName, (Args))`. +* The number `n` must equal the number of arguments. +* When mocking a const method, one must use `MOCK_CONST_METHODn`. +* When mocking a class template, the macro name must be suffixed with `_T`. +* In order to specify the call type, the macro name must be suffixed with + `_WITH_CALLTYPE`, and the call type is the first macro argument. + +Old macros and their new equivalents: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Simple
OldMOCK_METHOD1(Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int))
Const Method
OldMOCK_CONST_METHOD1(Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (const))
Method in a Class Template
OldMOCK_METHOD1_T(Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int))
Const Method in a Class Template
OldMOCK_CONST_METHOD1_T(Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (const))
Method with Call Type
OldMOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE)))
Const Method with Call Type
OldMOCK_CONST_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE)))
Method with Call Type in a Class Template
OldMOCK_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE)))
Const Method with Call Type in a Class Template
OldMOCK_CONST_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int))
NewMOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE)))
+ +### The Nice, the Strict, and the Naggy {#NiceStrictNaggy} + +If a mock method has no `EXPECT_CALL` spec but is called, we say that it's an +"uninteresting call", and the default action (which can be specified using +`ON_CALL()`) of the method will be taken. Currently, an uninteresting call will +also by default cause gMock to print a warning. + +However, sometimes you may want to ignore these uninteresting calls, and +sometimes you may want to treat them as errors. gMock lets you make the decision +on a per-mock-object basis. + +Suppose your test uses a mock class `MockFoo`: + +```cpp +TEST(...) { + MockFoo mock_foo; + EXPECT_CALL(mock_foo, DoThis()); + ... code that uses mock_foo ... +} +``` + +If a method of `mock_foo` other than `DoThis()` is called, you will get a +warning. However, if you rewrite your test to use `NiceMock` instead, +you can suppress the warning: + +```cpp +using ::testing::NiceMock; + +TEST(...) { + NiceMock mock_foo; + EXPECT_CALL(mock_foo, DoThis()); + ... code that uses mock_foo ... +} +``` + +`NiceMock` is a subclass of `MockFoo`, so it can be used wherever +`MockFoo` is accepted. + +It also works if `MockFoo`'s constructor takes some arguments, as +`NiceMock` "inherits" `MockFoo`'s constructors: + +```cpp +using ::testing::NiceMock; + +TEST(...) { + NiceMock mock_foo(5, "hi"); // Calls MockFoo(5, "hi"). + EXPECT_CALL(mock_foo, DoThis()); + ... code that uses mock_foo ... +} +``` + +The usage of `StrictMock` is similar, except that it makes all uninteresting +calls failures: + +```cpp +using ::testing::StrictMock; + +TEST(...) { + StrictMock mock_foo; + EXPECT_CALL(mock_foo, DoThis()); + ... code that uses mock_foo ... + + // The test will fail if a method of mock_foo other than DoThis() + // is called. +} +``` + +{: .callout .note} +NOTE: `NiceMock` and `StrictMock` only affects *uninteresting* calls (calls of +*methods* with no expectations); they do not affect *unexpected* calls (calls of +methods with expectations, but they don't match). See +[Understanding Uninteresting vs Unexpected Calls](#uninteresting-vs-unexpected). + +There are some caveats though (sadly they are side effects of C++'s +limitations): + +1. `NiceMock` and `StrictMock` only work for mock methods + defined using the `MOCK_METHOD` macro **directly** in the `MockFoo` class. + If a mock method is defined in a **base class** of `MockFoo`, the "nice" or + "strict" modifier may not affect it, depending on the compiler. In + particular, nesting `NiceMock` and `StrictMock` (e.g. + `NiceMock >`) is **not** supported. +2. `NiceMock` and `StrictMock` may not work correctly if the + destructor of `MockFoo` is not virtual. We would like to fix this, but it + requires cleaning up existing tests. + +Finally, you should be **very cautious** about when to use naggy or strict +mocks, as they tend to make tests more brittle and harder to maintain. When you +refactor your code without changing its externally visible behavior, ideally you +shouldn't need to update any tests. If your code interacts with a naggy mock, +however, you may start to get spammed with warnings as the result of your +change. Worse, if your code interacts with a strict mock, your tests may start +to fail and you'll be forced to fix them. Our general recommendation is to use +nice mocks (not yet the default) most of the time, use naggy mocks (the current +default) when developing or debugging tests, and use strict mocks only as the +last resort. + +### Simplifying the Interface without Breaking Existing Code {#SimplerInterfaces} + +Sometimes a method has a long list of arguments that is mostly uninteresting. +For example: + +```cpp +class LogSink { + public: + ... + virtual void send(LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct tm* tm_time, + const char* message, size_t message_len) = 0; +}; +``` + +This method's argument list is lengthy and hard to work with (the `message` +argument is not even 0-terminated). If we mock it as is, using the mock will be +awkward. If, however, we try to simplify this interface, we'll need to fix all +clients depending on it, which is often infeasible. + +The trick is to redispatch the method in the mock class: + +```cpp +class ScopedMockLog : public LogSink { + public: + ... + void send(LogSeverity severity, const char* full_filename, + const char* base_filename, int line, const tm* tm_time, + const char* message, size_t message_len) override { + // We are only interested in the log severity, full file name, and + // log message. + Log(severity, full_filename, std::string(message, message_len)); + } + + // Implements the mock method: + // + // void Log(LogSeverity severity, + // const string& file_path, + // const string& message); + MOCK_METHOD(void, Log, + (LogSeverity severity, const string& file_path, + const string& message)); +}; +``` + +By defining a new mock method with a trimmed argument list, we make the mock +class more user-friendly. + +This technique may also be applied to make overloaded methods more amenable to +mocking. For example, when overloads have been used to implement default +arguments: + +```cpp +class MockTurtleFactory : public TurtleFactory { + public: + Turtle* MakeTurtle(int length, int weight) override { ... } + Turtle* MakeTurtle(int length, int weight, int speed) override { ... } + + // the above methods delegate to this one: + MOCK_METHOD(Turtle*, DoMakeTurtle, ()); +}; +``` + +This allows tests that don't care which overload was invoked to avoid specifying +argument matchers: + +```cpp +ON_CALL(factory, DoMakeTurtle) + .WillByDefault(Return(MakeMockTurtle())); +``` + +### Alternative to Mocking Concrete Classes + +Often you may find yourself using classes that don't implement interfaces. In +order to test your code that uses such a class (let's call it `Concrete`), you +may be tempted to make the methods of `Concrete` virtual and then mock it. + +Try not to do that. + +Making a non-virtual function virtual is a big decision. It creates an extension +point where subclasses can tweak your class' behavior. This weakens your control +on the class because now it's harder to maintain the class invariants. You +should make a function virtual only when there is a valid reason for a subclass +to override it. + +Mocking concrete classes directly is problematic as it creates a tight coupling +between the class and the tests - any small change in the class may invalidate +your tests and make test maintenance a pain. + +To avoid such problems, many programmers have been practicing "coding to +interfaces": instead of talking to the `Concrete` class, your code would define +an interface and talk to it. Then you implement that interface as an adaptor on +top of `Concrete`. In tests, you can easily mock that interface to observe how +your code is doing. + +This technique incurs some overhead: + +* You pay the cost of virtual function calls (usually not a problem). +* There is more abstraction for the programmers to learn. + +However, it can also bring significant benefits in addition to better +testability: + +* `Concrete`'s API may not fit your problem domain very well, as you may not + be the only client it tries to serve. By designing your own interface, you + have a chance to tailor it to your need - you may add higher-level + functionalities, rename stuff, etc instead of just trimming the class. This + allows you to write your code (user of the interface) in a more natural way, + which means it will be more readable, more maintainable, and you'll be more + productive. +* If `Concrete`'s implementation ever has to change, you don't have to rewrite + everywhere it is used. Instead, you can absorb the change in your + implementation of the interface, and your other code and tests will be + insulated from this change. + +Some people worry that if everyone is practicing this technique, they will end +up writing lots of redundant code. This concern is totally understandable. +However, there are two reasons why it may not be the case: + +* Different projects may need to use `Concrete` in different ways, so the best + interfaces for them will be different. Therefore, each of them will have its + own domain-specific interface on top of `Concrete`, and they will not be the + same code. +* If enough projects want to use the same interface, they can always share it, + just like they have been sharing `Concrete`. You can check in the interface + and the adaptor somewhere near `Concrete` (perhaps in a `contrib` + sub-directory) and let many projects use it. + +You need to weigh the pros and cons carefully for your particular problem, but +I'd like to assure you that the Java community has been practicing this for a +long time and it's a proven effective technique applicable in a wide variety of +situations. :-) + +### Delegating Calls to a Fake {#DelegatingToFake} + +Some times you have a non-trivial fake implementation of an interface. For +example: + +```cpp +class Foo { + public: + virtual ~Foo() {} + virtual char DoThis(int n) = 0; + virtual void DoThat(const char* s, int* p) = 0; +}; + +class FakeFoo : public Foo { + public: + char DoThis(int n) override { + return (n > 0) ? '+' : + (n < 0) ? '-' : '0'; + } + + void DoThat(const char* s, int* p) override { + *p = strlen(s); + } +}; +``` + +Now you want to mock this interface such that you can set expectations on it. +However, you also want to use `FakeFoo` for the default behavior, as duplicating +it in the mock object is, well, a lot of work. + +When you define the mock class using gMock, you can have it delegate its default +action to a fake class you already have, using this pattern: + +```cpp +class MockFoo : public Foo { + public: + // Normal mock method definitions using gMock. + MOCK_METHOD(char, DoThis, (int n), (override)); + MOCK_METHOD(void, DoThat, (const char* s, int* p), (override)); + + // Delegates the default actions of the methods to a FakeFoo object. + // This must be called *before* the custom ON_CALL() statements. + void DelegateToFake() { + ON_CALL(*this, DoThis).WillByDefault([this](int n) { + return fake_.DoThis(n); + }); + ON_CALL(*this, DoThat).WillByDefault([this](const char* s, int* p) { + fake_.DoThat(s, p); + }); + } + + private: + FakeFoo fake_; // Keeps an instance of the fake in the mock. +}; +``` + +With that, you can use `MockFoo` in your tests as usual. Just remember that if +you don't explicitly set an action in an `ON_CALL()` or `EXPECT_CALL()`, the +fake will be called upon to do it.: + +```cpp +using ::testing::_; + +TEST(AbcTest, Xyz) { + MockFoo foo; + + foo.DelegateToFake(); // Enables the fake for delegation. + + // Put your ON_CALL(foo, ...)s here, if any. + + // No action specified, meaning to use the default action. + EXPECT_CALL(foo, DoThis(5)); + EXPECT_CALL(foo, DoThat(_, _)); + + int n = 0; + EXPECT_EQ(foo.DoThis(5), '+'); // FakeFoo::DoThis() is invoked. + foo.DoThat("Hi", &n); // FakeFoo::DoThat() is invoked. + EXPECT_EQ(n, 2); +} +``` + +**Some tips:** + +* If you want, you can still override the default action by providing your own + `ON_CALL()` or using `.WillOnce()` / `.WillRepeatedly()` in `EXPECT_CALL()`. +* In `DelegateToFake()`, you only need to delegate the methods whose fake + implementation you intend to use. + +* The general technique discussed here works for overloaded methods, but + you'll need to tell the compiler which version you mean. To disambiguate a + mock function (the one you specify inside the parentheses of `ON_CALL()`), + use [this technique](#SelectOverload); to disambiguate a fake function (the + one you place inside `Invoke()`), use a `static_cast` to specify the + function's type. For instance, if class `Foo` has methods `char DoThis(int + n)` and `bool DoThis(double x) const`, and you want to invoke the latter, + you need to write `Invoke(&fake_, static_cast(&FakeFoo::DoThis))` instead of `Invoke(&fake_, &FakeFoo::DoThis)` + (The strange-looking thing inside the angled brackets of `static_cast` is + the type of a function pointer to the second `DoThis()` method.). + +* Having to mix a mock and a fake is often a sign of something gone wrong. + Perhaps you haven't got used to the interaction-based way of testing yet. Or + perhaps your interface is taking on too many roles and should be split up. + Therefore, **don't abuse this**. We would only recommend to do it as an + intermediate step when you are refactoring your code. + +Regarding the tip on mixing a mock and a fake, here's an example on why it may +be a bad sign: Suppose you have a class `System` for low-level system +operations. In particular, it does file and I/O operations. And suppose you want +to test how your code uses `System` to do I/O, and you just want the file +operations to work normally. If you mock out the entire `System` class, you'll +have to provide a fake implementation for the file operation part, which +suggests that `System` is taking on too many roles. + +Instead, you can define a `FileOps` interface and an `IOOps` interface and split +`System`'s functionalities into the two. Then you can mock `IOOps` without +mocking `FileOps`. + +### Delegating Calls to a Real Object + +When using testing doubles (mocks, fakes, stubs, and etc), sometimes their +behaviors will differ from those of the real objects. This difference could be +either intentional (as in simulating an error such that you can test the error +handling code) or unintentional. If your mocks have different behaviors than the +real objects by mistake, you could end up with code that passes the tests but +fails in production. + +You can use the *delegating-to-real* technique to ensure that your mock has the +same behavior as the real object while retaining the ability to validate calls. +This technique is very similar to the [delegating-to-fake](#DelegatingToFake) +technique, the difference being that we use a real object instead of a fake. +Here's an example: + +```cpp +using ::testing::AtLeast; + +class MockFoo : public Foo { + public: + MockFoo() { + // By default, all calls are delegated to the real object. + ON_CALL(*this, DoThis).WillByDefault([this](int n) { + return real_.DoThis(n); + }); + ON_CALL(*this, DoThat).WillByDefault([this](const char* s, int* p) { + real_.DoThat(s, p); + }); + ... + } + MOCK_METHOD(char, DoThis, ...); + MOCK_METHOD(void, DoThat, ...); + ... + private: + Foo real_; +}; + +... + MockFoo mock; + EXPECT_CALL(mock, DoThis()) + .Times(3); + EXPECT_CALL(mock, DoThat("Hi")) + .Times(AtLeast(1)); + ... use mock in test ... +``` + +With this, gMock will verify that your code made the right calls (with the right +arguments, in the right order, called the right number of times, etc), and a +real object will answer the calls (so the behavior will be the same as in +production). This gives you the best of both worlds. + +### Delegating Calls to a Parent Class + +Ideally, you should code to interfaces, whose methods are all pure virtual. In +reality, sometimes you do need to mock a virtual method that is not pure (i.e, +it already has an implementation). For example: + +```cpp +class Foo { + public: + virtual ~Foo(); + + virtual void Pure(int n) = 0; + virtual int Concrete(const char* str) { ... } +}; + +class MockFoo : public Foo { + public: + // Mocking a pure method. + MOCK_METHOD(void, Pure, (int n), (override)); + // Mocking a concrete method. Foo::Concrete() is shadowed. + MOCK_METHOD(int, Concrete, (const char* str), (override)); +}; +``` + +Sometimes you may want to call `Foo::Concrete()` instead of +`MockFoo::Concrete()`. Perhaps you want to do it as part of a stub action, or +perhaps your test doesn't need to mock `Concrete()` at all (but it would be +oh-so painful to have to define a new mock class whenever you don't need to mock +one of its methods). + +You can call `Foo::Concrete()` inside an action by: + +```cpp +... + EXPECT_CALL(foo, Concrete).WillOnce([&foo](const char* str) { + return foo.Foo::Concrete(str); + }); +``` + +or tell the mock object that you don't want to mock `Concrete()`: + +```cpp +... + ON_CALL(foo, Concrete).WillByDefault([&foo](const char* str) { + return foo.Foo::Concrete(str); + }); +``` + +(Why don't we just write `{ return foo.Concrete(str); }`? If you do that, +`MockFoo::Concrete()` will be called (and cause an infinite recursion) since +`Foo::Concrete()` is virtual. That's just how C++ works.) + +## Using Matchers + +### Matching Argument Values Exactly + +You can specify exactly which arguments a mock method is expecting: + +```cpp +using ::testing::Return; +... + EXPECT_CALL(foo, DoThis(5)) + .WillOnce(Return('a')); + EXPECT_CALL(foo, DoThat("Hello", bar)); +``` + +### Using Simple Matchers + +You can use matchers to match arguments that have a certain property: + +```cpp +using ::testing::NotNull; +using ::testing::Return; +... + EXPECT_CALL(foo, DoThis(Ge(5))) // The argument must be >= 5. + .WillOnce(Return('a')); + EXPECT_CALL(foo, DoThat("Hello", NotNull())); + // The second argument must not be NULL. +``` + +A frequently used matcher is `_`, which matches anything: + +```cpp + EXPECT_CALL(foo, DoThat(_, NotNull())); +``` + +### Combining Matchers {#CombiningMatchers} + +You can build complex matchers from existing ones using `AllOf()`, +`AllOfArray()`, `AnyOf()`, `AnyOfArray()` and `Not()`: + +```cpp +using ::testing::AllOf; +using ::testing::Gt; +using ::testing::HasSubstr; +using ::testing::Ne; +using ::testing::Not; +... + // The argument must be > 5 and != 10. + EXPECT_CALL(foo, DoThis(AllOf(Gt(5), + Ne(10)))); + + // The first argument must not contain sub-string "blah". + EXPECT_CALL(foo, DoThat(Not(HasSubstr("blah")), + NULL)); +``` + +Matchers are function objects, and parametrized matchers can be composed just +like any other function. However because their types can be long and rarely +provide meaningful information, it can be easier to express them with C++14 +generic lambdas to avoid specifying types. For example, + +```cpp +using ::testing::Contains; +using ::testing::Property; + +inline constexpr auto HasFoo = [](const auto& f) { + return Property("foo", &MyClass::foo, Contains(f)); +}; +... + EXPECT_THAT(x, HasFoo("blah")); +``` + +### Casting Matchers {#SafeMatcherCast} + +gMock matchers are statically typed, meaning that the compiler can catch your +mistake if you use a matcher of the wrong type (for example, if you use `Eq(5)` +to match a `string` argument). Good for you! + +Sometimes, however, you know what you're doing and want the compiler to give you +some slack. One example is that you have a matcher for `long` and the argument +you want to match is `int`. While the two types aren't exactly the same, there +is nothing really wrong with using a `Matcher` to match an `int` - after +all, we can first convert the `int` argument to a `long` losslessly before +giving it to the matcher. + +To support this need, gMock gives you the `SafeMatcherCast(m)` function. It +casts a matcher `m` to type `Matcher`. To ensure safety, gMock checks that +(let `U` be the type `m` accepts : + +1. Type `T` can be *implicitly* cast to type `U`; +2. When both `T` and `U` are built-in arithmetic types (`bool`, integers, and + floating-point numbers), the conversion from `T` to `U` is not lossy (in + other words, any value representable by `T` can also be represented by `U`); + and +3. When `U` is a reference, `T` must also be a reference (as the underlying + matcher may be interested in the address of the `U` value). + +The code won't compile if any of these conditions isn't met. + +Here's one example: + +```cpp +using ::testing::SafeMatcherCast; + +// A base class and a child class. +class Base { ... }; +class Derived : public Base { ... }; + +class MockFoo : public Foo { + public: + MOCK_METHOD(void, DoThis, (Derived* derived), (override)); +}; + +... + MockFoo foo; + // m is a Matcher we got from somewhere. + EXPECT_CALL(foo, DoThis(SafeMatcherCast(m))); +``` + +If you find `SafeMatcherCast(m)` too limiting, you can use a similar function +`MatcherCast(m)`. The difference is that `MatcherCast` works as long as you +can `static_cast` type `T` to type `U`. + +`MatcherCast` essentially lets you bypass C++'s type system (`static_cast` isn't +always safe as it could throw away information, for example), so be careful not +to misuse/abuse it. + +### Selecting Between Overloaded Functions {#SelectOverload} + +If you expect an overloaded function to be called, the compiler may need some +help on which overloaded version it is. + +To disambiguate functions overloaded on the const-ness of this object, use the +`Const()` argument wrapper. + +```cpp +using ::testing::ReturnRef; + +class MockFoo : public Foo { + ... + MOCK_METHOD(Bar&, GetBar, (), (override)); + MOCK_METHOD(const Bar&, GetBar, (), (const, override)); +}; + +... + MockFoo foo; + Bar bar1, bar2; + EXPECT_CALL(foo, GetBar()) // The non-const GetBar(). + .WillOnce(ReturnRef(bar1)); + EXPECT_CALL(Const(foo), GetBar()) // The const GetBar(). + .WillOnce(ReturnRef(bar2)); +``` + +(`Const()` is defined by gMock and returns a `const` reference to its argument.) + +To disambiguate overloaded functions with the same number of arguments but +different argument types, you may need to specify the exact type of a matcher, +either by wrapping your matcher in `Matcher()`, or using a matcher whose +type is fixed (`TypedEq`, `An()`, etc): + +```cpp +using ::testing::An; +using ::testing::Matcher; +using ::testing::TypedEq; + +class MockPrinter : public Printer { + public: + MOCK_METHOD(void, Print, (int n), (override)); + MOCK_METHOD(void, Print, (char c), (override)); +}; + +TEST(PrinterTest, Print) { + MockPrinter printer; + + EXPECT_CALL(printer, Print(An())); // void Print(int); + EXPECT_CALL(printer, Print(Matcher(Lt(5)))); // void Print(int); + EXPECT_CALL(printer, Print(TypedEq('a'))); // void Print(char); + + printer.Print(3); + printer.Print(6); + printer.Print('a'); +} +``` + +### Performing Different Actions Based on the Arguments + +When a mock method is called, the *last* matching expectation that's still +active will be selected (think "newer overrides older"). So, you can make a +method do different things depending on its argument values like this: + +```cpp +using ::testing::_; +using ::testing::Lt; +using ::testing::Return; +... + // The default case. + EXPECT_CALL(foo, DoThis(_)) + .WillRepeatedly(Return('b')); + // The more specific case. + EXPECT_CALL(foo, DoThis(Lt(5))) + .WillRepeatedly(Return('a')); +``` + +Now, if `foo.DoThis()` is called with a value less than 5, `'a'` will be +returned; otherwise `'b'` will be returned. + +### Matching Multiple Arguments as a Whole + +Sometimes it's not enough to match the arguments individually. For example, we +may want to say that the first argument must be less than the second argument. +The `With()` clause allows us to match all arguments of a mock function as a +whole. For example, + +```cpp +using ::testing::_; +using ::testing::Ne; +using ::testing::Lt; +... + EXPECT_CALL(foo, InRange(Ne(0), _)) + .With(Lt()); +``` + +says that the first argument of `InRange()` must not be 0, and must be less than +the second argument. + +The expression inside `With()` must be a matcher of type `Matcher>`, where `A1`, ..., `An` are the types of the function arguments. + +You can also write `AllArgs(m)` instead of `m` inside `.With()`. The two forms +are equivalent, but `.With(AllArgs(Lt()))` is more readable than `.With(Lt())`. + +You can use `Args(m)` to match the `n` selected arguments (as a +tuple) against `m`. For example, + +```cpp +using ::testing::_; +using ::testing::AllOf; +using ::testing::Args; +using ::testing::Lt; +... + EXPECT_CALL(foo, Blah) + .With(AllOf(Args<0, 1>(Lt()), Args<1, 2>(Lt()))); +``` + +says that `Blah` will be called with arguments `x`, `y`, and `z` where `x < y < +z`. Note that in this example, it wasn't necessary to specify the positional +matchers. + +As a convenience and example, gMock provides some matchers for 2-tuples, +including the `Lt()` matcher above. See +[Multi-argument Matchers](reference/matchers.md#MultiArgMatchers) for the +complete list. + +Note that if you want to pass the arguments to a predicate of your own (e.g. +`.With(Args<0, 1>(Truly(&MyPredicate)))`), that predicate MUST be written to +take a `std::tuple` as its argument; gMock will pass the `n` selected arguments +as *one* single tuple to the predicate. + +### Using Matchers as Predicates + +Have you noticed that a matcher is just a fancy predicate that also knows how to +describe itself? Many existing algorithms take predicates as arguments (e.g. +those defined in STL's `` header), and it would be a shame if gMock +matchers were not allowed to participate. + +Luckily, you can use a matcher where a unary predicate functor is expected by +wrapping it inside the `Matches()` function. For example, + +```cpp +#include +#include + +using ::testing::Matches; +using ::testing::Ge; + +vector v; +... +// How many elements in v are >= 10? +const int count = count_if(v.begin(), v.end(), Matches(Ge(10))); +``` + +Since you can build complex matchers from simpler ones easily using gMock, this +gives you a way to conveniently construct composite predicates (doing the same +using STL's `` header is just painful). For example, here's a +predicate that's satisfied by any number that is >= 0, <= 100, and != 50: + +```cpp +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::Le; +using ::testing::Matches; +using ::testing::Ne; +... +Matches(AllOf(Ge(0), Le(100), Ne(50))) +``` + +### Using Matchers in googletest Assertions + +See [`EXPECT_THAT`](reference/assertions.md#EXPECT_THAT) in the Assertions +Reference. + +### Using Predicates as Matchers + +gMock provides a set of built-in matchers for matching arguments with expected +values—see the [Matchers Reference](reference/matchers.md) for more information. +In case you find the built-in set lacking, you can use an arbitrary unary +predicate function or functor as a matcher - as long as the predicate accepts a +value of the type you want. You do this by wrapping the predicate inside the +`Truly()` function, for example: + +```cpp +using ::testing::Truly; + +int IsEven(int n) { return (n % 2) == 0 ? 1 : 0; } +... + // Bar() must be called with an even number. + EXPECT_CALL(foo, Bar(Truly(IsEven))); +``` + +Note that the predicate function / functor doesn't have to return `bool`. It +works as long as the return value can be used as the condition in the statement +`if (condition) ...`. + +### Matching Arguments that Are Not Copyable + +When you do an `EXPECT_CALL(mock_obj, Foo(bar))`, gMock saves away a copy of +`bar`. When `Foo()` is called later, gMock compares the argument to `Foo()` with +the saved copy of `bar`. This way, you don't need to worry about `bar` being +modified or destroyed after the `EXPECT_CALL()` is executed. The same is true +when you use matchers like `Eq(bar)`, `Le(bar)`, and so on. + +But what if `bar` cannot be copied (i.e. has no copy constructor)? You could +define your own matcher function or callback and use it with `Truly()`, as the +previous couple of recipes have shown. Or, you may be able to get away from it +if you can guarantee that `bar` won't be changed after the `EXPECT_CALL()` is +executed. Just tell gMock that it should save a reference to `bar`, instead of a +copy of it. Here's how: + +```cpp +using ::testing::Eq; +using ::testing::Lt; +... + // Expects that Foo()'s argument == bar. + EXPECT_CALL(mock_obj, Foo(Eq(std::ref(bar)))); + + // Expects that Foo()'s argument < bar. + EXPECT_CALL(mock_obj, Foo(Lt(std::ref(bar)))); +``` + +Remember: if you do this, don't change `bar` after the `EXPECT_CALL()`, or the +result is undefined. + +### Validating a Member of an Object + +Often a mock function takes a reference to object as an argument. When matching +the argument, you may not want to compare the entire object against a fixed +object, as that may be over-specification. Instead, you may need to validate a +certain member variable or the result of a certain getter method of the object. +You can do this with `Field()` and `Property()`. More specifically, + +```cpp +Field(&Foo::bar, m) +``` + +is a matcher that matches a `Foo` object whose `bar` member variable satisfies +matcher `m`. + +```cpp +Property(&Foo::baz, m) +``` + +is a matcher that matches a `Foo` object whose `baz()` method returns a value +that satisfies matcher `m`. + +For example: + +| Expression | Description | +| :--------------------------- | :--------------------------------------- | +| `Field(&Foo::number, Ge(3))` | Matches `x` where `x.number >= 3`. | +| `Property(&Foo::name, StartsWith("John "))` | Matches `x` where `x.name()` starts with `"John "`. | + +Note that in `Property(&Foo::baz, ...)`, method `baz()` must take no argument +and be declared as `const`. Don't use `Property()` against member functions that +you do not own, because taking addresses of functions is fragile and generally +not part of the contract of the function. + +`Field()` and `Property()` can also match plain pointers to objects. For +instance, + +```cpp +using ::testing::Field; +using ::testing::Ge; +... +Field(&Foo::number, Ge(3)) +``` + +matches a plain pointer `p` where `p->number >= 3`. If `p` is `NULL`, the match +will always fail regardless of the inner matcher. + +What if you want to validate more than one members at the same time? Remember +that there are [`AllOf()` and `AllOfArray()`](#CombiningMatchers). + +Finally `Field()` and `Property()` provide overloads that take the field or +property names as the first argument to include it in the error message. This +can be useful when creating combined matchers. + +```cpp +using ::testing::AllOf; +using ::testing::Field; +using ::testing::Matcher; +using ::testing::SafeMatcherCast; + +Matcher IsFoo(const Foo& foo) { + return AllOf(Field("some_field", &Foo::some_field, foo.some_field), + Field("other_field", &Foo::other_field, foo.other_field), + Field("last_field", &Foo::last_field, foo.last_field)); +} +``` + +### Validating the Value Pointed to by a Pointer Argument + +C++ functions often take pointers as arguments. You can use matchers like +`IsNull()`, `NotNull()`, and other comparison matchers to match a pointer, but +what if you want to make sure the value *pointed to* by the pointer, instead of +the pointer itself, has a certain property? Well, you can use the `Pointee(m)` +matcher. + +`Pointee(m)` matches a pointer if and only if `m` matches the value the pointer +points to. For example: + +```cpp +using ::testing::Ge; +using ::testing::Pointee; +... + EXPECT_CALL(foo, Bar(Pointee(Ge(3)))); +``` + +expects `foo.Bar()` to be called with a pointer that points to a value greater +than or equal to 3. + +One nice thing about `Pointee()` is that it treats a `NULL` pointer as a match +failure, so you can write `Pointee(m)` instead of + +```cpp +using ::testing::AllOf; +using ::testing::NotNull; +using ::testing::Pointee; +... + AllOf(NotNull(), Pointee(m)) +``` + +without worrying that a `NULL` pointer will crash your test. + +Also, did we tell you that `Pointee()` works with both raw pointers **and** +smart pointers (`std::unique_ptr`, `std::shared_ptr`, etc)? + +What if you have a pointer to pointer? You guessed it - you can use nested +`Pointee()` to probe deeper inside the value. For example, +`Pointee(Pointee(Lt(3)))` matches a pointer that points to a pointer that points +to a number less than 3 (what a mouthful...). + +### Defining a Custom Matcher Class {#CustomMatcherClass} + +Most matchers can be simply defined using [the MATCHER* macros](#NewMatchers), +which are terse and flexible, and produce good error messages. However, these +macros are not very explicit about the interfaces they create and are not always +suitable, especially for matchers that will be widely reused. + +For more advanced cases, you may need to define your own matcher class. A custom +matcher allows you to test a specific invariant property of that object. Let's +take a look at how to do so. + +Imagine you have a mock function that takes an object of type `Foo`, which has +an `int bar()` method and an `int baz()` method. You want to constrain that the +argument's `bar()` value plus its `baz()` value is a given number. (This is an +invariant.) Here's how we can write and use a matcher class to do so: + +```cpp +class BarPlusBazEqMatcher { + public: + using is_gtest_matcher = void; + + explicit BarPlusBazEqMatcher(int expected_sum) + : expected_sum_(expected_sum) {} + + bool MatchAndExplain(const Foo& foo, + std::ostream* /* listener */) const { + return (foo.bar() + foo.baz()) == expected_sum_; + } + + void DescribeTo(std::ostream* os) const { + *os << "bar() + baz() equals " << expected_sum_; + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "bar() + baz() does not equal " << expected_sum_; + } + private: + const int expected_sum_; +}; + +::testing::Matcher BarPlusBazEq(int expected_sum) { + return BarPlusBazEqMatcher(expected_sum); +} + +... + Foo foo; + EXPECT_THAT(foo, BarPlusBazEq(5))...; +``` + +### Matching Containers + +Sometimes an STL container (e.g. list, vector, map, ...) is passed to a mock +function and you may want to validate it. Since most STL containers support the +`==` operator, you can write `Eq(expected_container)` or simply +`expected_container` to match a container exactly. + +Sometimes, though, you may want to be more flexible (for example, the first +element must be an exact match, but the second element can be any positive +number, and so on). Also, containers used in tests often have a small number of +elements, and having to define the expected container out-of-line is a bit of a +hassle. + +You can use the `ElementsAre()` or `UnorderedElementsAre()` matcher in such +cases: + +```cpp +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Gt; +... + MOCK_METHOD(void, Foo, (const vector& numbers), (override)); +... + EXPECT_CALL(mock, Foo(ElementsAre(1, Gt(0), _, 5))); +``` + +The above matcher says that the container must have 4 elements, which must be 1, +greater than 0, anything, and 5 respectively. + +If you instead write: + +```cpp +using ::testing::_; +using ::testing::Gt; +using ::testing::UnorderedElementsAre; +... + MOCK_METHOD(void, Foo, (const vector& numbers), (override)); +... + EXPECT_CALL(mock, Foo(UnorderedElementsAre(1, Gt(0), _, 5))); +``` + +It means that the container must have 4 elements, which (under some permutation) +must be 1, greater than 0, anything, and 5 respectively. + +As an alternative you can place the arguments in a C-style array and use +`ElementsAreArray()` or `UnorderedElementsAreArray()` instead: + +```cpp +using ::testing::ElementsAreArray; +... + // ElementsAreArray accepts an array of element values. + const int expected_vector1[] = {1, 5, 2, 4, ...}; + EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector1))); + + // Or, an array of element matchers. + Matcher expected_vector2[] = {1, Gt(2), _, 3, ...}; + EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector2))); +``` + +In case the array needs to be dynamically created (and therefore the array size +cannot be inferred by the compiler), you can give `ElementsAreArray()` an +additional argument to specify the array size: + +```cpp +using ::testing::ElementsAreArray; +... + int* const expected_vector3 = new int[count]; + ... fill expected_vector3 with values ... + EXPECT_CALL(mock, Foo(ElementsAreArray(expected_vector3, count))); +``` + +Use `Pair` when comparing maps or other associative containers. + +{% raw %} + +```cpp +using ::testing::UnorderedElementsAre; +using ::testing::Pair; +... + absl::flat_hash_map m = {{"a", 1}, {"b", 2}, {"c", 3}}; + EXPECT_THAT(m, UnorderedElementsAre( + Pair("a", 1), Pair("b", 2), Pair("c", 3))); +``` + +{% endraw %} + +**Tips:** + +* `ElementsAre*()` can be used to match *any* container that implements the + STL iterator pattern (i.e. it has a `const_iterator` type and supports + `begin()/end()`), not just the ones defined in STL. It will even work with + container types yet to be written - as long as they follows the above + pattern. +* You can use nested `ElementsAre*()` to match nested (multi-dimensional) + containers. +* If the container is passed by pointer instead of by reference, just write + `Pointee(ElementsAre*(...))`. +* The order of elements *matters* for `ElementsAre*()`. If you are using it + with containers whose element order are undefined (such as a + `std::unordered_map`) you should use `UnorderedElementsAre`. + +### Sharing Matchers + +Under the hood, a gMock matcher object consists of a pointer to a ref-counted +implementation object. Copying matchers is allowed and very efficient, as only +the pointer is copied. When the last matcher that references the implementation +object dies, the implementation object will be deleted. + +Therefore, if you have some complex matcher that you want to use again and +again, there is no need to build it every time. Just assign it to a matcher +variable and use that variable repeatedly! For example, + +```cpp +using ::testing::AllOf; +using ::testing::Gt; +using ::testing::Le; +using ::testing::Matcher; +... + Matcher in_range = AllOf(Gt(5), Le(10)); + ... use in_range as a matcher in multiple EXPECT_CALLs ... +``` + +### Matchers must have no side-effects {#PureMatchers} + +{: .callout .warning} +WARNING: gMock does not guarantee when or how many times a matcher will be +invoked. Therefore, all matchers must be *purely functional*: they cannot have +any side effects, and the match result must not depend on anything other than +the matcher's parameters and the value being matched. + +This requirement must be satisfied no matter how a matcher is defined (e.g., if +it is one of the standard matchers, or a custom matcher). In particular, a +matcher can never call a mock function, as that will affect the state of the +mock object and gMock. + +## Setting Expectations + +### Knowing When to Expect {#UseOnCall} + +**`ON_CALL`** is likely the *single most under-utilized construct* in gMock. + +There are basically two constructs for defining the behavior of a mock object: +`ON_CALL` and `EXPECT_CALL`. The difference? `ON_CALL` defines what happens when +a mock method is called, but doesn't imply any expectation on the method +being called. `EXPECT_CALL` not only defines the behavior, but also sets an +expectation that the method will be called with the given arguments, for the +given number of times (and *in the given order* when you specify the order +too). + +Since `EXPECT_CALL` does more, isn't it better than `ON_CALL`? Not really. Every +`EXPECT_CALL` adds a constraint on the behavior of the code under test. Having +more constraints than necessary is *baaad* - even worse than not having enough +constraints. + +This may be counter-intuitive. How could tests that verify more be worse than +tests that verify less? Isn't verification the whole point of tests? + +The answer lies in *what* a test should verify. **A good test verifies the +contract of the code.** If a test over-specifies, it doesn't leave enough +freedom to the implementation. As a result, changing the implementation without +breaking the contract (e.g. refactoring and optimization), which should be +perfectly fine to do, can break such tests. Then you have to spend time fixing +them, only to see them broken again the next time the implementation is changed. + +Keep in mind that one doesn't have to verify more than one property in one test. +In fact, **it's a good style to verify only one thing in one test.** If you do +that, a bug will likely break only one or two tests instead of dozens (which +case would you rather debug?). If you are also in the habit of giving tests +descriptive names that tell what they verify, you can often easily guess what's +wrong just from the test log itself. + +So use `ON_CALL` by default, and only use `EXPECT_CALL` when you actually intend +to verify that the call is made. For example, you may have a bunch of `ON_CALL`s +in your test fixture to set the common mock behavior shared by all tests in the +same group, and write (scarcely) different `EXPECT_CALL`s in different `TEST_F`s +to verify different aspects of the code's behavior. Compared with the style +where each `TEST` has many `EXPECT_CALL`s, this leads to tests that are more +resilient to implementational changes (and thus less likely to require +maintenance) and makes the intent of the tests more obvious (so they are easier +to maintain when you do need to maintain them). + +If you are bothered by the "Uninteresting mock function call" message printed +when a mock method without an `EXPECT_CALL` is called, you may use a `NiceMock` +instead to suppress all such messages for the mock object, or suppress the +message for specific methods by adding `EXPECT_CALL(...).Times(AnyNumber())`. DO +NOT suppress it by blindly adding an `EXPECT_CALL(...)`, or you'll have a test +that's a pain to maintain. + +### Ignoring Uninteresting Calls + +If you are not interested in how a mock method is called, just don't say +anything about it. In this case, if the method is ever called, gMock will +perform its default action to allow the test program to continue. If you are not +happy with the default action taken by gMock, you can override it using +`DefaultValue::Set()` (described [here](#DefaultValue)) or `ON_CALL()`. + +Please note that once you expressed interest in a particular mock method (via +`EXPECT_CALL()`), all invocations to it must match some expectation. If this +function is called but the arguments don't match any `EXPECT_CALL()` statement, +it will be an error. + +### Disallowing Unexpected Calls + +If a mock method shouldn't be called at all, explicitly say so: + +```cpp +using ::testing::_; +... + EXPECT_CALL(foo, Bar(_)) + .Times(0); +``` + +If some calls to the method are allowed, but the rest are not, just list all the +expected calls: + +```cpp +using ::testing::AnyNumber; +using ::testing::Gt; +... + EXPECT_CALL(foo, Bar(5)); + EXPECT_CALL(foo, Bar(Gt(10))) + .Times(AnyNumber()); +``` + +A call to `foo.Bar()` that doesn't match any of the `EXPECT_CALL()` statements +will be an error. + +### Understanding Uninteresting vs Unexpected Calls {#uninteresting-vs-unexpected} + +*Uninteresting* calls and *unexpected* calls are different concepts in gMock. +*Very* different. + +A call `x.Y(...)` is **uninteresting** if there's *not even a single* +`EXPECT_CALL(x, Y(...))` set. In other words, the test isn't interested in the +`x.Y()` method at all, as evident in that the test doesn't care to say anything +about it. + +A call `x.Y(...)` is **unexpected** if there are *some* `EXPECT_CALL(x, +Y(...))`s set, but none of them matches the call. Put another way, the test is +interested in the `x.Y()` method (therefore it explicitly sets some +`EXPECT_CALL` to verify how it's called); however, the verification fails as the +test doesn't expect this particular call to happen. + +**An unexpected call is always an error,** as the code under test doesn't behave +the way the test expects it to behave. + +**By default, an uninteresting call is not an error,** as it violates no +constraint specified by the test. (gMock's philosophy is that saying nothing +means there is no constraint.) However, it leads to a warning, as it *might* +indicate a problem (e.g. the test author might have forgotten to specify a +constraint). + +In gMock, `NiceMock` and `StrictMock` can be used to make a mock class "nice" or +"strict". How does this affect uninteresting calls and unexpected calls? + +A **nice mock** suppresses uninteresting call *warnings*. It is less chatty than +the default mock, but otherwise is the same. If a test fails with a default +mock, it will also fail using a nice mock instead. And vice versa. Don't expect +making a mock nice to change the test's result. + +A **strict mock** turns uninteresting call warnings into errors. So making a +mock strict may change the test's result. + +Let's look at an example: + +```cpp +TEST(...) { + NiceMock mock_registry; + EXPECT_CALL(mock_registry, GetDomainOwner("google.com")) + .WillRepeatedly(Return("Larry Page")); + + // Use mock_registry in code under test. + ... &mock_registry ... +} +``` + +The sole `EXPECT_CALL` here says that all calls to `GetDomainOwner()` must have +`"google.com"` as the argument. If `GetDomainOwner("yahoo.com")` is called, it +will be an unexpected call, and thus an error. *Having a nice mock doesn't +change the severity of an unexpected call.* + +So how do we tell gMock that `GetDomainOwner()` can be called with some other +arguments as well? The standard technique is to add a "catch all" `EXPECT_CALL`: + +```cpp + EXPECT_CALL(mock_registry, GetDomainOwner(_)) + .Times(AnyNumber()); // catches all other calls to this method. + EXPECT_CALL(mock_registry, GetDomainOwner("google.com")) + .WillRepeatedly(Return("Larry Page")); +``` + +Remember that `_` is the wildcard matcher that matches anything. With this, if +`GetDomainOwner("google.com")` is called, it will do what the second +`EXPECT_CALL` says; if it is called with a different argument, it will do what +the first `EXPECT_CALL` says. + +Note that the order of the two `EXPECT_CALL`s is important, as a newer +`EXPECT_CALL` takes precedence over an older one. + +For more on uninteresting calls, nice mocks, and strict mocks, read +["The Nice, the Strict, and the Naggy"](#NiceStrictNaggy). + +### Ignoring Uninteresting Arguments {#ParameterlessExpectations} + +If your test doesn't care about the parameters (it only cares about the number +or order of calls), you can often simply omit the parameter list: + +```cpp + // Expect foo.Bar( ... ) twice with any arguments. + EXPECT_CALL(foo, Bar).Times(2); + + // Delegate to the given method whenever the factory is invoked. + ON_CALL(foo_factory, MakeFoo) + .WillByDefault(&BuildFooForTest); +``` + +This functionality is only available when a method is not overloaded; to prevent +unexpected behavior it is a compilation error to try to set an expectation on a +method where the specific overload is ambiguous. You can work around this by +supplying a [simpler mock interface](#SimplerInterfaces) than the mocked class +provides. + +This pattern is also useful when the arguments are interesting, but match logic +is substantially complex. You can leave the argument list unspecified and use +SaveArg actions to [save the values for later verification](#SaveArgVerify). If +you do that, you can easily differentiate calling the method the wrong number of +times from calling it with the wrong arguments. + +### Expecting Ordered Calls {#OrderedCalls} + +Although an `EXPECT_CALL()` statement defined later takes precedence when gMock +tries to match a function call with an expectation, by default calls don't have +to happen in the order `EXPECT_CALL()` statements are written. For example, if +the arguments match the matchers in the second `EXPECT_CALL()`, but not those in +the first and third, then the second expectation will be used. + +If you would rather have all calls occur in the order of the expectations, put +the `EXPECT_CALL()` statements in a block where you define a variable of type +`InSequence`: + +```cpp +using ::testing::_; +using ::testing::InSequence; + + { + InSequence s; + + EXPECT_CALL(foo, DoThis(5)); + EXPECT_CALL(bar, DoThat(_)) + .Times(2); + EXPECT_CALL(foo, DoThis(6)); + } +``` + +In this example, we expect a call to `foo.DoThis(5)`, followed by two calls to +`bar.DoThat()` where the argument can be anything, which are in turn followed by +a call to `foo.DoThis(6)`. If a call occurred out-of-order, gMock will report an +error. + +### Expecting Partially Ordered Calls {#PartialOrder} + +Sometimes requiring everything to occur in a predetermined order can lead to +brittle tests. For example, we may care about `A` occurring before both `B` and +`C`, but aren't interested in the relative order of `B` and `C`. In this case, +the test should reflect our real intent, instead of being overly constraining. + +gMock allows you to impose an arbitrary DAG (directed acyclic graph) on the +calls. One way to express the DAG is to use the +[`After` clause](reference/mocking.md#EXPECT_CALL.After) of `EXPECT_CALL`. + +Another way is via the `InSequence()` clause (not the same as the `InSequence` +class), which we borrowed from jMock 2. It's less flexible than `After()`, but +more convenient when you have long chains of sequential calls, as it doesn't +require you to come up with different names for the expectations in the chains. +Here's how it works: + +If we view `EXPECT_CALL()` statements as nodes in a graph, and add an edge from +node A to node B wherever A must occur before B, we can get a DAG. We use the +term "sequence" to mean a directed path in this DAG. Now, if we decompose the +DAG into sequences, we just need to know which sequences each `EXPECT_CALL()` +belongs to in order to be able to reconstruct the original DAG. + +So, to specify the partial order on the expectations we need to do two things: +first to define some `Sequence` objects, and then for each `EXPECT_CALL()` say +which `Sequence` objects it is part of. + +Expectations in the same sequence must occur in the order they are written. For +example, + +```cpp +using ::testing::Sequence; +... + Sequence s1, s2; + + EXPECT_CALL(foo, A()) + .InSequence(s1, s2); + EXPECT_CALL(bar, B()) + .InSequence(s1); + EXPECT_CALL(bar, C()) + .InSequence(s2); + EXPECT_CALL(foo, D()) + .InSequence(s2); +``` + +specifies the following DAG (where `s1` is `A -> B`, and `s2` is `A -> C -> D`): + +```text + +---> B + | + A ---| + | + +---> C ---> D +``` + +This means that A must occur before B and C, and C must occur before D. There's +no restriction about the order other than these. + +### Controlling When an Expectation Retires + +When a mock method is called, gMock only considers expectations that are still +active. An expectation is active when created, and becomes inactive (aka +*retires*) when a call that has to occur later has occurred. For example, in + +```cpp +using ::testing::_; +using ::testing::Sequence; +... + Sequence s1, s2; + + EXPECT_CALL(log, Log(WARNING, _, "File too large.")) // #1 + .Times(AnyNumber()) + .InSequence(s1, s2); + EXPECT_CALL(log, Log(WARNING, _, "Data set is empty.")) // #2 + .InSequence(s1); + EXPECT_CALL(log, Log(WARNING, _, "User not found.")) // #3 + .InSequence(s2); +``` + +as soon as either #2 or #3 is matched, #1 will retire. If a warning `"File too +large."` is logged after this, it will be an error. + +Note that an expectation doesn't retire automatically when it's saturated. For +example, + +```cpp +using ::testing::_; +... + EXPECT_CALL(log, Log(WARNING, _, _)); // #1 + EXPECT_CALL(log, Log(WARNING, _, "File too large.")); // #2 +``` + +says that there will be exactly one warning with the message `"File too +large."`. If the second warning contains this message too, #2 will match again +and result in an upper-bound-violated error. + +If this is not what you want, you can ask an expectation to retire as soon as it +becomes saturated: + +```cpp +using ::testing::_; +... + EXPECT_CALL(log, Log(WARNING, _, _)); // #1 + EXPECT_CALL(log, Log(WARNING, _, "File too large.")) // #2 + .RetiresOnSaturation(); +``` + +Here #2 can be used only once, so if you have two warnings with the message +`"File too large."`, the first will match #2 and the second will match #1 - +there will be no error. + +## Using Actions + +### Returning References from Mock Methods + +If a mock function's return type is a reference, you need to use `ReturnRef()` +instead of `Return()` to return a result: + +```cpp +using ::testing::ReturnRef; + +class MockFoo : public Foo { + public: + MOCK_METHOD(Bar&, GetBar, (), (override)); +}; +... + MockFoo foo; + Bar bar; + EXPECT_CALL(foo, GetBar()) + .WillOnce(ReturnRef(bar)); +... +``` + +### Returning Live Values from Mock Methods + +The `Return(x)` action saves a copy of `x` when the action is created, and +always returns the same value whenever it's executed. Sometimes you may want to +instead return the *live* value of `x` (i.e. its value at the time when the +action is *executed*.). Use either `ReturnRef()` or `ReturnPointee()` for this +purpose. + +If the mock function's return type is a reference, you can do it using +`ReturnRef(x)`, as shown in the previous recipe ("Returning References from Mock +Methods"). However, gMock doesn't let you use `ReturnRef()` in a mock function +whose return type is not a reference, as doing that usually indicates a user +error. So, what shall you do? + +Though you may be tempted, DO NOT use `std::ref()`: + +```cpp +using ::testing::Return; + +class MockFoo : public Foo { + public: + MOCK_METHOD(int, GetValue, (), (override)); +}; +... + int x = 0; + MockFoo foo; + EXPECT_CALL(foo, GetValue()) + .WillRepeatedly(Return(std::ref(x))); // Wrong! + x = 42; + EXPECT_EQ(foo.GetValue(), 42); +``` + +Unfortunately, it doesn't work here. The above code will fail with error: + +```text +Value of: foo.GetValue() + Actual: 0 +Expected: 42 +``` + +The reason is that `Return(*value*)` converts `value` to the actual return type +of the mock function at the time when the action is *created*, not when it is +*executed*. (This behavior was chosen for the action to be safe when `value` is +a proxy object that references some temporary objects.) As a result, +`std::ref(x)` is converted to an `int` value (instead of a `const int&`) when +the expectation is set, and `Return(std::ref(x))` will always return 0. + +`ReturnPointee(pointer)` was provided to solve this problem specifically. It +returns the value pointed to by `pointer` at the time the action is *executed*: + +```cpp +using ::testing::ReturnPointee; +... + int x = 0; + MockFoo foo; + EXPECT_CALL(foo, GetValue()) + .WillRepeatedly(ReturnPointee(&x)); // Note the & here. + x = 42; + EXPECT_EQ(foo.GetValue(), 42); // This will succeed now. +``` + +### Combining Actions + +Want to do more than one thing when a function is called? That's fine. `DoAll()` +allows you to do a sequence of actions every time. Only the return value of the +last action in the sequence will be used. + +```cpp +using ::testing::_; +using ::testing::DoAll; + +class MockFoo : public Foo { + public: + MOCK_METHOD(bool, Bar, (int n), (override)); +}; +... + EXPECT_CALL(foo, Bar(_)) + .WillOnce(DoAll(action_1, + action_2, + ... + action_n)); +``` + +### Verifying Complex Arguments {#SaveArgVerify} + +If you want to verify that a method is called with a particular argument but the +match criteria is complex, it can be difficult to distinguish between +cardinality failures (calling the method the wrong number of times) and argument +match failures. Similarly, if you are matching multiple parameters, it may not +be easy to distinguishing which argument failed to match. For example: + +```cpp + // Not ideal: this could fail because of a problem with arg1 or arg2, or maybe + // just the method wasn't called. + EXPECT_CALL(foo, SendValues(_, ElementsAre(1, 4, 4, 7), EqualsProto( ... ))); +``` + +You can instead save the arguments and test them individually: + +```cpp + EXPECT_CALL(foo, SendValues) + .WillOnce(DoAll(SaveArg<1>(&actual_array), SaveArg<2>(&actual_proto))); + ... run the test + EXPECT_THAT(actual_array, ElementsAre(1, 4, 4, 7)); + EXPECT_THAT(actual_proto, EqualsProto( ... )); +``` + +### Mocking Side Effects {#MockingSideEffects} + +Sometimes a method exhibits its effect not via returning a value but via side +effects. For example, it may change some global state or modify an output +argument. To mock side effects, in general you can define your own action by +implementing `::testing::ActionInterface`. + +If all you need to do is to change an output argument, the built-in +`SetArgPointee()` action is convenient: + +```cpp +using ::testing::_; +using ::testing::SetArgPointee; + +class MockMutator : public Mutator { + public: + MOCK_METHOD(void, Mutate, (bool mutate, int* value), (override)); + ... +} +... + MockMutator mutator; + EXPECT_CALL(mutator, Mutate(true, _)) + .WillOnce(SetArgPointee<1>(5)); +``` + +In this example, when `mutator.Mutate()` is called, we will assign 5 to the +`int` variable pointed to by argument #1 (0-based). + +`SetArgPointee()` conveniently makes an internal copy of the value you pass to +it, removing the need to keep the value in scope and alive. The implication +however is that the value must have a copy constructor and assignment operator. + +If the mock method also needs to return a value as well, you can chain +`SetArgPointee()` with `Return()` using `DoAll()`, remembering to put the +`Return()` statement last: + +```cpp +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; + +class MockMutator : public Mutator { + public: + ... + MOCK_METHOD(bool, MutateInt, (int* value), (override)); +} +... + MockMutator mutator; + EXPECT_CALL(mutator, MutateInt(_)) + .WillOnce(DoAll(SetArgPointee<0>(5), + Return(true))); +``` + +Note, however, that if you use the `ReturnOKWith()` method, it will override the +values provided by `SetArgPointee()` in the response parameters of your function +call. + +If the output argument is an array, use the `SetArrayArgument(first, last)` +action instead. It copies the elements in source range `[first, last)` to the +array pointed to by the `N`-th (0-based) argument: + +```cpp +using ::testing::NotNull; +using ::testing::SetArrayArgument; + +class MockArrayMutator : public ArrayMutator { + public: + MOCK_METHOD(void, Mutate, (int* values, int num_values), (override)); + ... +} +... + MockArrayMutator mutator; + int values[5] = {1, 2, 3, 4, 5}; + EXPECT_CALL(mutator, Mutate(NotNull(), 5)) + .WillOnce(SetArrayArgument<0>(values, values + 5)); +``` + +This also works when the argument is an output iterator: + +```cpp +using ::testing::_; +using ::testing::SetArrayArgument; + +class MockRolodex : public Rolodex { + public: + MOCK_METHOD(void, GetNames, (std::back_insert_iterator>), + (override)); + ... +} +... + MockRolodex rolodex; + vector names = {"George", "John", "Thomas"}; + EXPECT_CALL(rolodex, GetNames(_)) + .WillOnce(SetArrayArgument<0>(names.begin(), names.end())); +``` + +### Changing a Mock Object's Behavior Based on the State + +If you expect a call to change the behavior of a mock object, you can use +`::testing::InSequence` to specify different behaviors before and after the +call: + +```cpp +using ::testing::InSequence; +using ::testing::Return; + +... + { + InSequence seq; + EXPECT_CALL(my_mock, IsDirty()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(my_mock, Flush()); + EXPECT_CALL(my_mock, IsDirty()) + .WillRepeatedly(Return(false)); + } + my_mock.FlushIfDirty(); +``` + +This makes `my_mock.IsDirty()` return `true` before `my_mock.Flush()` is called +and return `false` afterwards. + +If the behavior change is more complex, you can store the effects in a variable +and make a mock method get its return value from that variable: + +```cpp +using ::testing::_; +using ::testing::SaveArg; +using ::testing::Return; + +ACTION_P(ReturnPointee, p) { return *p; } +... + int previous_value = 0; + EXPECT_CALL(my_mock, GetPrevValue) + .WillRepeatedly(ReturnPointee(&previous_value)); + EXPECT_CALL(my_mock, UpdateValue) + .WillRepeatedly(SaveArg<0>(&previous_value)); + my_mock.DoSomethingToUpdateValue(); +``` + +Here `my_mock.GetPrevValue()` will always return the argument of the last +`UpdateValue()` call. + +### Setting the Default Value for a Return Type {#DefaultValue} + +If a mock method's return type is a built-in C++ type or pointer, by default it +will return 0 when invoked. Also, in C++ 11 and above, a mock method whose +return type has a default constructor will return a default-constructed value by +default. You only need to specify an action if this default value doesn't work +for you. + +Sometimes, you may want to change this default value, or you may want to specify +a default value for types gMock doesn't know about. You can do this using the +`::testing::DefaultValue` class template: + +```cpp +using ::testing::DefaultValue; + +class MockFoo : public Foo { + public: + MOCK_METHOD(Bar, CalculateBar, (), (override)); +}; + + +... + Bar default_bar; + // Sets the default return value for type Bar. + DefaultValue::Set(default_bar); + + MockFoo foo; + + // We don't need to specify an action here, as the default + // return value works for us. + EXPECT_CALL(foo, CalculateBar()); + + foo.CalculateBar(); // This should return default_bar. + + // Unsets the default return value. + DefaultValue::Clear(); +``` + +Please note that changing the default value for a type can make your tests hard +to understand. We recommend you to use this feature judiciously. For example, +you may want to make sure the `Set()` and `Clear()` calls are right next to the +code that uses your mock. + +### Setting the Default Actions for a Mock Method + +You've learned how to change the default value of a given type. However, this +may be too coarse for your purpose: perhaps you have two mock methods with the +same return type and you want them to have different behaviors. The `ON_CALL()` +macro allows you to customize your mock's behavior at the method level: + +```cpp +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Gt; +using ::testing::Return; +... + ON_CALL(foo, Sign(_)) + .WillByDefault(Return(-1)); + ON_CALL(foo, Sign(0)) + .WillByDefault(Return(0)); + ON_CALL(foo, Sign(Gt(0))) + .WillByDefault(Return(1)); + + EXPECT_CALL(foo, Sign(_)) + .Times(AnyNumber()); + + foo.Sign(5); // This should return 1. + foo.Sign(-9); // This should return -1. + foo.Sign(0); // This should return 0. +``` + +As you may have guessed, when there are more than one `ON_CALL()` statements, +the newer ones in the order take precedence over the older ones. In other words, +the **last** one that matches the function arguments will be used. This matching +order allows you to set up the common behavior in a mock object's constructor or +the test fixture's set-up phase and specialize the mock's behavior later. + +Note that both `ON_CALL` and `EXPECT_CALL` have the same "later statements take +precedence" rule, but they don't interact. That is, `EXPECT_CALL`s have their +own precedence order distinct from the `ON_CALL` precedence order. + +### Using Functions/Methods/Functors/Lambdas as Actions {#FunctionsAsActions} + +If the built-in actions don't suit you, you can use an existing callable +(function, `std::function`, method, functor, lambda) as an action. + +```cpp +using ::testing::_; using ::testing::Invoke; + +class MockFoo : public Foo { + public: + MOCK_METHOD(int, Sum, (int x, int y), (override)); + MOCK_METHOD(bool, ComplexJob, (int x), (override)); +}; + +int CalculateSum(int x, int y) { return x + y; } +int Sum3(int x, int y, int z) { return x + y + z; } + +class Helper { + public: + bool ComplexJob(int x); +}; + +... + MockFoo foo; + Helper helper; + EXPECT_CALL(foo, Sum(_, _)) + .WillOnce(&CalculateSum) + .WillRepeatedly(Invoke(NewPermanentCallback(Sum3, 1))); + EXPECT_CALL(foo, ComplexJob(_)) + .WillOnce(Invoke(&helper, &Helper::ComplexJob)) + .WillOnce([] { return true; }) + .WillRepeatedly([](int x) { return x > 0; }); + + foo.Sum(5, 6); // Invokes CalculateSum(5, 6). + foo.Sum(2, 3); // Invokes Sum3(1, 2, 3). + foo.ComplexJob(10); // Invokes helper.ComplexJob(10). + foo.ComplexJob(-1); // Invokes the inline lambda. +``` + +The only requirement is that the type of the function, etc must be *compatible* +with the signature of the mock function, meaning that the latter's arguments (if +it takes any) can be implicitly converted to the corresponding arguments of the +former, and the former's return type can be implicitly converted to that of the +latter. So, you can invoke something whose type is *not* exactly the same as the +mock function, as long as it's safe to do so - nice, huh? + +Note that: + +* The action takes ownership of the callback and will delete it when the + action itself is destructed. +* If the type of a callback is derived from a base callback type `C`, you need + to implicitly cast it to `C` to resolve the overloading, e.g. + + ```cpp + using ::testing::Invoke; + ... + ResultCallback* is_ok = ...; + ... Invoke(is_ok) ...; // This works. + + BlockingClosure* done = new BlockingClosure; + ... Invoke(implicit_cast(done)) ...; // The cast is necessary. + ``` + +### Using Functions with Extra Info as Actions + +The function or functor you call using `Invoke()` must have the same number of +arguments as the mock function you use it for. Sometimes you may have a function +that takes more arguments, and you are willing to pass in the extra arguments +yourself to fill the gap. You can do this in gMock using callbacks with +pre-bound arguments. Here's an example: + +```cpp +using ::testing::Invoke; + +class MockFoo : public Foo { + public: + MOCK_METHOD(char, DoThis, (int n), (override)); +}; + +char SignOfSum(int x, int y) { + const int sum = x + y; + return (sum > 0) ? '+' : (sum < 0) ? '-' : '0'; +} + +TEST_F(FooTest, Test) { + MockFoo foo; + + EXPECT_CALL(foo, DoThis(2)) + .WillOnce(Invoke(NewPermanentCallback(SignOfSum, 5))); + EXPECT_EQ(foo.DoThis(2), '+'); // Invokes SignOfSum(5, 2). +} +``` + +### Invoking a Function/Method/Functor/Lambda/Callback Without Arguments + +`Invoke()` passes the mock function's arguments to the function, etc being +invoked such that the callee has the full context of the call to work with. If +the invoked function is not interested in some or all of the arguments, it can +simply ignore them. + +Yet, a common pattern is that a test author wants to invoke a function without +the arguments of the mock function. She could do that using a wrapper function +that throws away the arguments before invoking an underlining nullary function. +Needless to say, this can be tedious and obscures the intent of the test. + +There are two solutions to this problem. First, you can pass any callable of +zero args as an action. Alternatively, use `InvokeWithoutArgs()`, which is like +`Invoke()` except that it doesn't pass the mock function's arguments to the +callee. Here's an example of each: + +```cpp +using ::testing::_; +using ::testing::InvokeWithoutArgs; + +class MockFoo : public Foo { + public: + MOCK_METHOD(bool, ComplexJob, (int n), (override)); +}; + +bool Job1() { ... } +bool Job2(int n, char c) { ... } + +... + MockFoo foo; + EXPECT_CALL(foo, ComplexJob(_)) + .WillOnce([] { Job1(); }); + .WillOnce(InvokeWithoutArgs(NewPermanentCallback(Job2, 5, 'a'))); + + foo.ComplexJob(10); // Invokes Job1(). + foo.ComplexJob(20); // Invokes Job2(5, 'a'). +``` + +Note that: + +* The action takes ownership of the callback and will delete it when the + action itself is destructed. +* If the type of a callback is derived from a base callback type `C`, you need + to implicitly cast it to `C` to resolve the overloading, e.g. + + ```cpp + using ::testing::InvokeWithoutArgs; + ... + ResultCallback* is_ok = ...; + ... InvokeWithoutArgs(is_ok) ...; // This works. + + BlockingClosure* done = ...; + ... InvokeWithoutArgs(implicit_cast(done)) ...; + // The cast is necessary. + ``` + +### Invoking an Argument of the Mock Function + +Sometimes a mock function will receive a function pointer, a functor (in other +words, a "callable") as an argument, e.g. + +```cpp +class MockFoo : public Foo { + public: + MOCK_METHOD(bool, DoThis, (int n, (ResultCallback1* callback)), + (override)); +}; +``` + +and you may want to invoke this callable argument: + +```cpp +using ::testing::_; +... + MockFoo foo; + EXPECT_CALL(foo, DoThis(_, _)) + .WillOnce(...); + // Will execute callback->Run(5), where callback is the + // second argument DoThis() receives. +``` + +{: .callout .note} +NOTE: The section below is legacy documentation from before C++ had lambdas: + +Arghh, you need to refer to a mock function argument but C++ has no lambda +(yet), so you have to define your own action. :-( Or do you really? + +Well, gMock has an action to solve *exactly* this problem: + +```cpp +InvokeArgument(arg_1, arg_2, ..., arg_m) +``` + +will invoke the `N`-th (0-based) argument the mock function receives, with +`arg_1`, `arg_2`, ..., and `arg_m`. No matter if the argument is a function +pointer, a functor, or a callback. gMock handles them all. + +With that, you could write: + +```cpp +using ::testing::_; +using ::testing::InvokeArgument; +... + EXPECT_CALL(foo, DoThis(_, _)) + .WillOnce(InvokeArgument<1>(5)); + // Will execute callback->Run(5), where callback is the + // second argument DoThis() receives. +``` + +What if the callable takes an argument by reference? No problem - just wrap it +inside `std::ref()`: + +```cpp + ... + MOCK_METHOD(bool, Bar, + ((ResultCallback2* callback)), + (override)); + ... + using ::testing::_; + using ::testing::InvokeArgument; + ... + MockFoo foo; + Helper helper; + ... + EXPECT_CALL(foo, Bar(_)) + .WillOnce(InvokeArgument<0>(5, std::ref(helper))); + // std::ref(helper) guarantees that a reference to helper, not a copy of + // it, will be passed to the callback. +``` + +What if the callable takes an argument by reference and we do **not** wrap the +argument in `std::ref()`? Then `InvokeArgument()` will *make a copy* of the +argument, and pass a *reference to the copy*, instead of a reference to the +original value, to the callable. This is especially handy when the argument is a +temporary value: + +```cpp + ... + MOCK_METHOD(bool, DoThat, (bool (*f)(const double& x, const string& s)), + (override)); + ... + using ::testing::_; + using ::testing::InvokeArgument; + ... + MockFoo foo; + ... + EXPECT_CALL(foo, DoThat(_)) + .WillOnce(InvokeArgument<0>(5.0, string("Hi"))); + // Will execute (*f)(5.0, string("Hi")), where f is the function pointer + // DoThat() receives. Note that the values 5.0 and string("Hi") are + // temporary and dead once the EXPECT_CALL() statement finishes. Yet + // it's fine to perform this action later, since a copy of the values + // are kept inside the InvokeArgument action. +``` + +### Ignoring an Action's Result + +Sometimes you have an action that returns *something*, but you need an action +that returns `void` (perhaps you want to use it in a mock function that returns +`void`, or perhaps it needs to be used in `DoAll()` and it's not the last in the +list). `IgnoreResult()` lets you do that. For example: + +```cpp +using ::testing::_; +using ::testing::DoAll; +using ::testing::IgnoreResult; +using ::testing::Return; + +int Process(const MyData& data); +string DoSomething(); + +class MockFoo : public Foo { + public: + MOCK_METHOD(void, Abc, (const MyData& data), (override)); + MOCK_METHOD(bool, Xyz, (), (override)); +}; + + ... + MockFoo foo; + EXPECT_CALL(foo, Abc(_)) + // .WillOnce(Invoke(Process)); + // The above line won't compile as Process() returns int but Abc() needs + // to return void. + .WillOnce(IgnoreResult(Process)); + EXPECT_CALL(foo, Xyz()) + .WillOnce(DoAll(IgnoreResult(DoSomething), + // Ignores the string DoSomething() returns. + Return(true))); +``` + +Note that you **cannot** use `IgnoreResult()` on an action that already returns +`void`. Doing so will lead to ugly compiler errors. + +### Selecting an Action's Arguments {#SelectingArgs} + +Say you have a mock function `Foo()` that takes seven arguments, and you have a +custom action that you want to invoke when `Foo()` is called. Trouble is, the +custom action only wants three arguments: + +```cpp +using ::testing::_; +using ::testing::Invoke; +... + MOCK_METHOD(bool, Foo, + (bool visible, const string& name, int x, int y, + (const map>), double& weight, double min_weight, + double max_wight)); +... +bool IsVisibleInQuadrant1(bool visible, int x, int y) { + return visible && x >= 0 && y >= 0; +} +... + EXPECT_CALL(mock, Foo) + .WillOnce(Invoke(IsVisibleInQuadrant1)); // Uh, won't compile. :-( +``` + +To please the compiler God, you need to define an "adaptor" that has the same +signature as `Foo()` and calls the custom action with the right arguments: + +```cpp +using ::testing::_; +using ::testing::Invoke; +... +bool MyIsVisibleInQuadrant1(bool visible, const string& name, int x, int y, + const map, double>& weight, + double min_weight, double max_wight) { + return IsVisibleInQuadrant1(visible, x, y); +} +... + EXPECT_CALL(mock, Foo) + .WillOnce(Invoke(MyIsVisibleInQuadrant1)); // Now it works. +``` + +But isn't this awkward? + +gMock provides a generic *action adaptor*, so you can spend your time minding +more important business than writing your own adaptors. Here's the syntax: + +```cpp +WithArgs(action) +``` + +creates an action that passes the arguments of the mock function at the given +indices (0-based) to the inner `action` and performs it. Using `WithArgs`, our +original example can be written as: + +```cpp +using ::testing::_; +using ::testing::Invoke; +using ::testing::WithArgs; +... + EXPECT_CALL(mock, Foo) + .WillOnce(WithArgs<0, 2, 3>(Invoke(IsVisibleInQuadrant1))); // No need to define your own adaptor. +``` + +For better readability, gMock also gives you: + +* `WithoutArgs(action)` when the inner `action` takes *no* argument, and +* `WithArg(action)` (no `s` after `Arg`) when the inner `action` takes + *one* argument. + +As you may have realized, `InvokeWithoutArgs(...)` is just syntactic sugar for +`WithoutArgs(Invoke(...))`. + +Here are more tips: + +* The inner action used in `WithArgs` and friends does not have to be + `Invoke()` -- it can be anything. +* You can repeat an argument in the argument list if necessary, e.g. + `WithArgs<2, 3, 3, 5>(...)`. +* You can change the order of the arguments, e.g. `WithArgs<3, 2, 1>(...)`. +* The types of the selected arguments do *not* have to match the signature of + the inner action exactly. It works as long as they can be implicitly + converted to the corresponding arguments of the inner action. For example, + if the 4-th argument of the mock function is an `int` and `my_action` takes + a `double`, `WithArg<4>(my_action)` will work. + +### Ignoring Arguments in Action Functions + +The [selecting-an-action's-arguments](#SelectingArgs) recipe showed us one way +to make a mock function and an action with incompatible argument lists fit +together. The downside is that wrapping the action in `WithArgs<...>()` can get +tedious for people writing the tests. + +If you are defining a function (or method, functor, lambda, callback) to be used +with `Invoke*()`, and you are not interested in some of its arguments, an +alternative to `WithArgs` is to declare the uninteresting arguments as `Unused`. +This makes the definition less cluttered and less fragile in case the types of +the uninteresting arguments change. It could also increase the chance the action +function can be reused. For example, given + +```cpp + public: + MOCK_METHOD(double, Foo, double(const string& label, double x, double y), + (override)); + MOCK_METHOD(double, Bar, (int index, double x, double y), (override)); +``` + +instead of + +```cpp +using ::testing::_; +using ::testing::Invoke; + +double DistanceToOriginWithLabel(const string& label, double x, double y) { + return sqrt(x*x + y*y); +} +double DistanceToOriginWithIndex(int index, double x, double y) { + return sqrt(x*x + y*y); +} +... + EXPECT_CALL(mock, Foo("abc", _, _)) + .WillOnce(Invoke(DistanceToOriginWithLabel)); + EXPECT_CALL(mock, Bar(5, _, _)) + .WillOnce(Invoke(DistanceToOriginWithIndex)); +``` + +you could write + +```cpp +using ::testing::_; +using ::testing::Invoke; +using ::testing::Unused; + +double DistanceToOrigin(Unused, double x, double y) { + return sqrt(x*x + y*y); +} +... + EXPECT_CALL(mock, Foo("abc", _, _)) + .WillOnce(Invoke(DistanceToOrigin)); + EXPECT_CALL(mock, Bar(5, _, _)) + .WillOnce(Invoke(DistanceToOrigin)); +``` + +### Sharing Actions + +Just like matchers, a gMock action object consists of a pointer to a ref-counted +implementation object. Therefore copying actions is also allowed and very +efficient. When the last action that references the implementation object dies, +the implementation object will be deleted. + +If you have some complex action that you want to use again and again, you may +not have to build it from scratch every time. If the action doesn't have an +internal state (i.e. if it always does the same thing no matter how many times +it has been called), you can assign it to an action variable and use that +variable repeatedly. For example: + +```cpp +using ::testing::Action; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; +... + Action set_flag = DoAll(SetArgPointee<0>(5), + Return(true)); + ... use set_flag in .WillOnce() and .WillRepeatedly() ... +``` + +However, if the action has its own state, you may be surprised if you share the +action object. Suppose you have an action factory `IncrementCounter(init)` which +creates an action that increments and returns a counter whose initial value is +`init`, using two actions created from the same expression and using a shared +action will exhibit different behaviors. Example: + +```cpp + EXPECT_CALL(foo, DoThis()) + .WillRepeatedly(IncrementCounter(0)); + EXPECT_CALL(foo, DoThat()) + .WillRepeatedly(IncrementCounter(0)); + foo.DoThis(); // Returns 1. + foo.DoThis(); // Returns 2. + foo.DoThat(); // Returns 1 - DoThat() uses a different + // counter than DoThis()'s. +``` + +versus + +```cpp +using ::testing::Action; +... + Action increment = IncrementCounter(0); + EXPECT_CALL(foo, DoThis()) + .WillRepeatedly(increment); + EXPECT_CALL(foo, DoThat()) + .WillRepeatedly(increment); + foo.DoThis(); // Returns 1. + foo.DoThis(); // Returns 2. + foo.DoThat(); // Returns 3 - the counter is shared. +``` + +### Testing Asynchronous Behavior + +One oft-encountered problem with gMock is that it can be hard to test +asynchronous behavior. Suppose you had a `EventQueue` class that you wanted to +test, and you created a separate `EventDispatcher` interface so that you could +easily mock it out. However, the implementation of the class fired all the +events on a background thread, which made test timings difficult. You could just +insert `sleep()` statements and hope for the best, but that makes your test +behavior nondeterministic. A better way is to use gMock actions and +`Notification` objects to force your asynchronous test to behave synchronously. + +```cpp +class MockEventDispatcher : public EventDispatcher { + MOCK_METHOD(bool, DispatchEvent, (int32), (override)); +}; + +TEST(EventQueueTest, EnqueueEventTest) { + MockEventDispatcher mock_event_dispatcher; + EventQueue event_queue(&mock_event_dispatcher); + + const int32 kEventId = 321; + absl::Notification done; + EXPECT_CALL(mock_event_dispatcher, DispatchEvent(kEventId)) + .WillOnce([&done] { done.Notify(); }); + + event_queue.EnqueueEvent(kEventId); + done.WaitForNotification(); +} +``` + +In the example above, we set our normal gMock expectations, but then add an +additional action to notify the `Notification` object. Now we can just call +`Notification::WaitForNotification()` in the main thread to wait for the +asynchronous call to finish. After that, our test suite is complete and we can +safely exit. + +{: .callout .note} +Note: this example has a downside: namely, if the expectation is not satisfied, +our test will run forever. It will eventually time-out and fail, but it will +take longer and be slightly harder to debug. To alleviate this problem, you can +use `WaitForNotificationWithTimeout(ms)` instead of `WaitForNotification()`. + +## Misc Recipes on Using gMock + +### Mocking Methods That Use Move-Only Types + +C++11 introduced *move-only types*. A move-only-typed value can be moved from +one object to another, but cannot be copied. `std::unique_ptr` is probably +the most commonly used move-only type. + +Mocking a method that takes and/or returns move-only types presents some +challenges, but nothing insurmountable. This recipe shows you how you can do it. +Note that the support for move-only method arguments was only introduced to +gMock in April 2017; in older code, you may find more complex +[workarounds](#LegacyMoveOnly) for lack of this feature. + +Let’s say we are working on a fictional project that lets one post and share +snippets called “buzzesâ€. Your code uses these types: + +```cpp +enum class AccessLevel { kInternal, kPublic }; + +class Buzz { + public: + explicit Buzz(AccessLevel access) { ... } + ... +}; + +class Buzzer { + public: + virtual ~Buzzer() {} + virtual std::unique_ptr MakeBuzz(StringPiece text) = 0; + virtual bool ShareBuzz(std::unique_ptr buzz, int64_t timestamp) = 0; + ... +}; +``` + +A `Buzz` object represents a snippet being posted. A class that implements the +`Buzzer` interface is capable of creating and sharing `Buzz`es. Methods in +`Buzzer` may return a `unique_ptr` or take a `unique_ptr`. Now we +need to mock `Buzzer` in our tests. + +To mock a method that accepts or returns move-only types, you just use the +familiar `MOCK_METHOD` syntax as usual: + +```cpp +class MockBuzzer : public Buzzer { + public: + MOCK_METHOD(std::unique_ptr, MakeBuzz, (StringPiece text), (override)); + MOCK_METHOD(bool, ShareBuzz, (std::unique_ptr buzz, int64_t timestamp), + (override)); +}; +``` + +Now that we have the mock class defined, we can use it in tests. In the +following code examples, we assume that we have defined a `MockBuzzer` object +named `mock_buzzer_`: + +```cpp + MockBuzzer mock_buzzer_; +``` + +First let’s see how we can set expectations on the `MakeBuzz()` method, which +returns a `unique_ptr`. + +As usual, if you set an expectation without an action (i.e. the `.WillOnce()` or +`.WillRepeatedly()` clause), when that expectation fires, the default action for +that method will be taken. Since `unique_ptr<>` has a default constructor that +returns a null `unique_ptr`, that’s what you’ll get if you don’t specify an +action: + +```cpp +using ::testing::IsNull; +... + // Use the default action. + EXPECT_CALL(mock_buzzer_, MakeBuzz("hello")); + + // Triggers the previous EXPECT_CALL. + EXPECT_THAT(mock_buzzer_.MakeBuzz("hello"), IsNull()); +``` + +If you are not happy with the default action, you can tweak it as usual; see +[Setting Default Actions](#OnCall). + +If you just need to return a move-only value, you can use it in combination with +`WillOnce`. For example: + +```cpp + EXPECT_CALL(mock_buzzer_, MakeBuzz("hello")) + .WillOnce(Return(std::make_unique(AccessLevel::kInternal))); + EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("hello")); +``` + +Quiz time! What do you think will happen if a `Return` action is performed more +than once (e.g. you write `... .WillRepeatedly(Return(std::move(...)));`)? Come +think of it, after the first time the action runs, the source value will be +consumed (since it’s a move-only value), so the next time around, there’s no +value to move from -- you’ll get a run-time error that `Return(std::move(...))` +can only be run once. + +If you need your mock method to do more than just moving a pre-defined value, +remember that you can always use a lambda or a callable object, which can do +pretty much anything you want: + +```cpp + EXPECT_CALL(mock_buzzer_, MakeBuzz("x")) + .WillRepeatedly([](StringPiece text) { + return std::make_unique(AccessLevel::kInternal); + }); + + EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("x")); + EXPECT_NE(nullptr, mock_buzzer_.MakeBuzz("x")); +``` + +Every time this `EXPECT_CALL` fires, a new `unique_ptr` will be created +and returned. You cannot do this with `Return(std::make_unique<...>(...))`. + +That covers returning move-only values; but how do we work with methods +accepting move-only arguments? The answer is that they work normally, although +some actions will not compile when any of method's arguments are move-only. You +can always use `Return`, or a [lambda or functor](#FunctionsAsActions): + +```cpp + using ::testing::Unused; + + EXPECT_CALL(mock_buzzer_, ShareBuzz(NotNull(), _)).WillOnce(Return(true)); + EXPECT_TRUE(mock_buzzer_.ShareBuzz(std::make_unique(AccessLevel::kInternal)), + 0); + + EXPECT_CALL(mock_buzzer_, ShareBuzz(_, _)).WillOnce( + [](std::unique_ptr buzz, Unused) { return buzz != nullptr; }); + EXPECT_FALSE(mock_buzzer_.ShareBuzz(nullptr, 0)); +``` + +Many built-in actions (`WithArgs`, `WithoutArgs`,`DeleteArg`, `SaveArg`, ...) +could in principle support move-only arguments, but the support for this is not +implemented yet. If this is blocking you, please file a bug. + +A few actions (e.g. `DoAll`) copy their arguments internally, so they can never +work with non-copyable objects; you'll have to use functors instead. + +#### Legacy workarounds for move-only types {#LegacyMoveOnly} + +Support for move-only function arguments was only introduced to gMock in April +of 2017. In older code, you may encounter the following workaround for the lack +of this feature (it is no longer necessary - we're including it just for +reference): + +```cpp +class MockBuzzer : public Buzzer { + public: + MOCK_METHOD(bool, DoShareBuzz, (Buzz* buzz, Time timestamp)); + bool ShareBuzz(std::unique_ptr buzz, Time timestamp) override { + return DoShareBuzz(buzz.get(), timestamp); + } +}; +``` + +The trick is to delegate the `ShareBuzz()` method to a mock method (let’s call +it `DoShareBuzz()`) that does not take move-only parameters. Then, instead of +setting expectations on `ShareBuzz()`, you set them on the `DoShareBuzz()` mock +method: + +```cpp + MockBuzzer mock_buzzer_; + EXPECT_CALL(mock_buzzer_, DoShareBuzz(NotNull(), _)); + + // When one calls ShareBuzz() on the MockBuzzer like this, the call is + // forwarded to DoShareBuzz(), which is mocked. Therefore this statement + // will trigger the above EXPECT_CALL. + mock_buzzer_.ShareBuzz(std::make_unique(AccessLevel::kInternal), 0); +``` + +### Making the Compilation Faster + +Believe it or not, the *vast majority* of the time spent on compiling a mock +class is in generating its constructor and destructor, as they perform +non-trivial tasks (e.g. verification of the expectations). What's more, mock +methods with different signatures have different types and thus their +constructors/destructors need to be generated by the compiler separately. As a +result, if you mock many different types of methods, compiling your mock class +can get really slow. + +If you are experiencing slow compilation, you can move the definition of your +mock class' constructor and destructor out of the class body and into a `.cc` +file. This way, even if you `#include` your mock class in N files, the compiler +only needs to generate its constructor and destructor once, resulting in a much +faster compilation. + +Let's illustrate the idea using an example. Here's the definition of a mock +class before applying this recipe: + +```cpp +// File mock_foo.h. +... +class MockFoo : public Foo { + public: + // Since we don't declare the constructor or the destructor, + // the compiler will generate them in every translation unit + // where this mock class is used. + + MOCK_METHOD(int, DoThis, (), (override)); + MOCK_METHOD(bool, DoThat, (const char* str), (override)); + ... more mock methods ... +}; +``` + +After the change, it would look like: + +```cpp +// File mock_foo.h. +... +class MockFoo : public Foo { + public: + // The constructor and destructor are declared, but not defined, here. + MockFoo(); + virtual ~MockFoo(); + + MOCK_METHOD(int, DoThis, (), (override)); + MOCK_METHOD(bool, DoThat, (const char* str), (override)); + ... more mock methods ... +}; +``` + +and + +```cpp +// File mock_foo.cc. +#include "path/to/mock_foo.h" + +// The definitions may appear trivial, but the functions actually do a +// lot of things through the constructors/destructors of the member +// variables used to implement the mock methods. +MockFoo::MockFoo() {} +MockFoo::~MockFoo() {} +``` + +### Forcing a Verification + +When it's being destroyed, your friendly mock object will automatically verify +that all expectations on it have been satisfied, and will generate googletest +failures if not. This is convenient as it leaves you with one less thing to +worry about. That is, unless you are not sure if your mock object will be +destroyed. + +How could it be that your mock object won't eventually be destroyed? Well, it +might be created on the heap and owned by the code you are testing. Suppose +there's a bug in that code and it doesn't delete the mock object properly - you +could end up with a passing test when there's actually a bug. + +Using a heap checker is a good idea and can alleviate the concern, but its +implementation is not 100% reliable. So, sometimes you do want to *force* gMock +to verify a mock object before it is (hopefully) destructed. You can do this +with `Mock::VerifyAndClearExpectations(&mock_object)`: + +```cpp +TEST(MyServerTest, ProcessesRequest) { + using ::testing::Mock; + + MockFoo* const foo = new MockFoo; + EXPECT_CALL(*foo, ...)...; + // ... other expectations ... + + // server now owns foo. + MyServer server(foo); + server.ProcessRequest(...); + + // In case that server's destructor will forget to delete foo, + // this will verify the expectations anyway. + Mock::VerifyAndClearExpectations(foo); +} // server is destroyed when it goes out of scope here. +``` + +{: .callout .tip} +**Tip:** The `Mock::VerifyAndClearExpectations()` function returns a `bool` to +indicate whether the verification was successful (`true` for yes), so you can +wrap that function call inside a `ASSERT_TRUE()` if there is no point going +further when the verification has failed. + +Do not set new expectations after verifying and clearing a mock after its use. +Setting expectations after code that exercises the mock has undefined behavior. +See [Using Mocks in Tests](gmock_for_dummies.md#using-mocks-in-tests) for more +information. + +### Using Checkpoints {#UsingCheckPoints} + +Sometimes you might want to test a mock object's behavior in phases whose sizes +are each manageable, or you might want to set more detailed expectations about +which API calls invoke which mock functions. + +A technique you can use is to put the expectations in a sequence and insert +calls to a dummy "checkpoint" function at specific places. Then you can verify +that the mock function calls do happen at the right time. For example, if you +are exercising the code: + +```cpp + Foo(1); + Foo(2); + Foo(3); +``` + +and want to verify that `Foo(1)` and `Foo(3)` both invoke `mock.Bar("a")`, but +`Foo(2)` doesn't invoke anything, you can write: + +```cpp +using ::testing::MockFunction; + +TEST(FooTest, InvokesBarCorrectly) { + MyMock mock; + // Class MockFunction has exactly one mock method. It is named + // Call() and has type F. + MockFunction check; + { + InSequence s; + + EXPECT_CALL(mock, Bar("a")); + EXPECT_CALL(check, Call("1")); + EXPECT_CALL(check, Call("2")); + EXPECT_CALL(mock, Bar("a")); + } + Foo(1); + check.Call("1"); + Foo(2); + check.Call("2"); + Foo(3); +} +``` + +The expectation spec says that the first `Bar("a")` call must happen before +checkpoint "1", the second `Bar("a")` call must happen after checkpoint "2", and +nothing should happen between the two checkpoints. The explicit checkpoints make +it clear which `Bar("a")` is called by which call to `Foo()`. + +### Mocking Destructors + +Sometimes you want to make sure a mock object is destructed at the right time, +e.g. after `bar->A()` is called but before `bar->B()` is called. We already know +that you can specify constraints on the [order](#OrderedCalls) of mock function +calls, so all we need to do is to mock the destructor of the mock function. + +This sounds simple, except for one problem: a destructor is a special function +with special syntax and special semantics, and the `MOCK_METHOD` macro doesn't +work for it: + +```cpp +MOCK_METHOD(void, ~MockFoo, ()); // Won't compile! +``` + +The good news is that you can use a simple pattern to achieve the same effect. +First, add a mock function `Die()` to your mock class and call it in the +destructor, like this: + +```cpp +class MockFoo : public Foo { + ... + // Add the following two lines to the mock class. + MOCK_METHOD(void, Die, ()); + ~MockFoo() override { Die(); } +}; +``` + +(If the name `Die()` clashes with an existing symbol, choose another name.) Now, +we have translated the problem of testing when a `MockFoo` object dies to +testing when its `Die()` method is called: + +```cpp + MockFoo* foo = new MockFoo; + MockBar* bar = new MockBar; + ... + { + InSequence s; + + // Expects *foo to die after bar->A() and before bar->B(). + EXPECT_CALL(*bar, A()); + EXPECT_CALL(*foo, Die()); + EXPECT_CALL(*bar, B()); + } +``` + +And that's that. + +### Using gMock and Threads {#UsingThreads} + +In a **unit** test, it's best if you could isolate and test a piece of code in a +single-threaded context. That avoids race conditions and dead locks, and makes +debugging your test much easier. + +Yet most programs are multi-threaded, and sometimes to test something we need to +pound on it from more than one thread. gMock works for this purpose too. + +Remember the steps for using a mock: + +1. Create a mock object `foo`. +2. Set its default actions and expectations using `ON_CALL()` and + `EXPECT_CALL()`. +3. The code under test calls methods of `foo`. +4. Optionally, verify and reset the mock. +5. Destroy the mock yourself, or let the code under test destroy it. The + destructor will automatically verify it. + +If you follow the following simple rules, your mocks and threads can live +happily together: + +* Execute your *test code* (as opposed to the code being tested) in *one* + thread. This makes your test easy to follow. +* Obviously, you can do step #1 without locking. +* When doing step #2 and #5, make sure no other thread is accessing `foo`. + Obvious too, huh? +* #3 and #4 can be done either in one thread or in multiple threads - anyway + you want. gMock takes care of the locking, so you don't have to do any - + unless required by your test logic. + +If you violate the rules (for example, if you set expectations on a mock while +another thread is calling its methods), you get undefined behavior. That's not +fun, so don't do it. + +gMock guarantees that the action for a mock function is done in the same thread +that called the mock function. For example, in + +```cpp + EXPECT_CALL(mock, Foo(1)) + .WillOnce(action1); + EXPECT_CALL(mock, Foo(2)) + .WillOnce(action2); +``` + +if `Foo(1)` is called in thread 1 and `Foo(2)` is called in thread 2, gMock will +execute `action1` in thread 1 and `action2` in thread 2. + +gMock does *not* impose a sequence on actions performed in different threads +(doing so may create deadlocks as the actions may need to cooperate). This means +that the execution of `action1` and `action2` in the above example *may* +interleave. If this is a problem, you should add proper synchronization logic to +`action1` and `action2` to make the test thread-safe. + +Also, remember that `DefaultValue` is a global resource that potentially +affects *all* living mock objects in your program. Naturally, you won't want to +mess with it from multiple threads or when there still are mocks in action. + +### Controlling How Much Information gMock Prints + +When gMock sees something that has the potential of being an error (e.g. a mock +function with no expectation is called, a.k.a. an uninteresting call, which is +allowed but perhaps you forgot to explicitly ban the call), it prints some +warning messages, including the arguments of the function, the return value, and +the stack trace. Hopefully this will remind you to take a look and see if there +is indeed a problem. + +Sometimes you are confident that your tests are correct and may not appreciate +such friendly messages. Some other times, you are debugging your tests or +learning about the behavior of the code you are testing, and wish you could +observe every mock call that happens (including argument values, the return +value, and the stack trace). Clearly, one size doesn't fit all. + +You can control how much gMock tells you using the `--gmock_verbose=LEVEL` +command-line flag, where `LEVEL` is a string with three possible values: + +* `info`: gMock will print all informational messages, warnings, and errors + (most verbose). At this setting, gMock will also log any calls to the + `ON_CALL/EXPECT_CALL` macros. It will include a stack trace in + "uninteresting call" warnings. +* `warning`: gMock will print both warnings and errors (less verbose); it will + omit the stack traces in "uninteresting call" warnings. This is the default. +* `error`: gMock will print errors only (least verbose). + +Alternatively, you can adjust the value of that flag from within your tests like +so: + +```cpp + ::testing::FLAGS_gmock_verbose = "error"; +``` + +If you find gMock printing too many stack frames with its informational or +warning messages, remember that you can control their amount with the +`--gtest_stack_trace_depth=max_depth` flag. + +Now, judiciously use the right flag to enable gMock serve you better! + +### Gaining Super Vision into Mock Calls + +You have a test using gMock. It fails: gMock tells you some expectations aren't +satisfied. However, you aren't sure why: Is there a typo somewhere in the +matchers? Did you mess up the order of the `EXPECT_CALL`s? Or is the code under +test doing something wrong? How can you find out the cause? + +Won't it be nice if you have X-ray vision and can actually see the trace of all +`EXPECT_CALL`s and mock method calls as they are made? For each call, would you +like to see its actual argument values and which `EXPECT_CALL` gMock thinks it +matches? If you still need some help to figure out who made these calls, how +about being able to see the complete stack trace at each mock call? + +You can unlock this power by running your test with the `--gmock_verbose=info` +flag. For example, given the test program: + +```cpp +#include + +using ::testing::_; +using ::testing::HasSubstr; +using ::testing::Return; + +class MockFoo { + public: + MOCK_METHOD(void, F, (const string& x, const string& y)); +}; + +TEST(Foo, Bar) { + MockFoo mock; + EXPECT_CALL(mock, F(_, _)).WillRepeatedly(Return()); + EXPECT_CALL(mock, F("a", "b")); + EXPECT_CALL(mock, F("c", HasSubstr("d"))); + + mock.F("a", "good"); + mock.F("a", "b"); +} +``` + +if you run it with `--gmock_verbose=info`, you will see this output: + +```shell +[ RUN ] Foo.Bar + +foo_test.cc:14: EXPECT_CALL(mock, F(_, _)) invoked +Stack trace: ... + +foo_test.cc:15: EXPECT_CALL(mock, F("a", "b")) invoked +Stack trace: ... + +foo_test.cc:16: EXPECT_CALL(mock, F("c", HasSubstr("d"))) invoked +Stack trace: ... + +foo_test.cc:14: Mock function call matches EXPECT_CALL(mock, F(_, _))... + Function call: F(@0x7fff7c8dad40"a",@0x7fff7c8dad10"good") +Stack trace: ... + +foo_test.cc:15: Mock function call matches EXPECT_CALL(mock, F("a", "b"))... + Function call: F(@0x7fff7c8dada0"a",@0x7fff7c8dad70"b") +Stack trace: ... + +foo_test.cc:16: Failure +Actual function call count doesn't match EXPECT_CALL(mock, F("c", HasSubstr("d")))... + Expected: to be called once + Actual: never called - unsatisfied and active +[ FAILED ] Foo.Bar +``` + +Suppose the bug is that the `"c"` in the third `EXPECT_CALL` is a typo and +should actually be `"a"`. With the above message, you should see that the actual +`F("a", "good")` call is matched by the first `EXPECT_CALL`, not the third as +you thought. From that it should be obvious that the third `EXPECT_CALL` is +written wrong. Case solved. + +If you are interested in the mock call trace but not the stack traces, you can +combine `--gmock_verbose=info` with `--gtest_stack_trace_depth=0` on the test +command line. + +### Running Tests in Emacs + +If you build and run your tests in Emacs using the `M-x google-compile` command +(as many googletest users do), the source file locations of gMock and googletest +errors will be highlighted. Just press `` on one of them and you'll be +taken to the offending line. Or, you can just type `C-x`` to jump to the next +error. + +To make it even easier, you can add the following lines to your `~/.emacs` file: + +```text +(global-set-key "\M-m" 'google-compile) ; m is for make +(global-set-key [M-down] 'next-error) +(global-set-key [M-up] '(lambda () (interactive) (next-error -1))) +``` + +Then you can type `M-m` to start a build (if you want to run the test as well, +just make sure `foo_test.run` or `runtests` is in the build command you supply +after typing `M-m`), or `M-up`/`M-down` to move back and forth between errors. + +## Extending gMock + +### Writing New Matchers Quickly {#NewMatchers} + +{: .callout .warning} +WARNING: gMock does not guarantee when or how many times a matcher will be +invoked. Therefore, all matchers must be functionally pure. See +[this section](#PureMatchers) for more details. + +The `MATCHER*` family of macros can be used to define custom matchers easily. +The syntax: + +```cpp +MATCHER(name, description_string_expression) { statements; } +``` + +will define a matcher with the given name that executes the statements, which +must return a `bool` to indicate if the match succeeds. Inside the statements, +you can refer to the value being matched by `arg`, and refer to its type by +`arg_type`. + +The *description string* is a `string`-typed expression that documents what the +matcher does, and is used to generate the failure message when the match fails. +It can (and should) reference the special `bool` variable `negation`, and should +evaluate to the description of the matcher when `negation` is `false`, or that +of the matcher's negation when `negation` is `true`. + +For convenience, we allow the description string to be empty (`""`), in which +case gMock will use the sequence of words in the matcher name as the +description. + +For example: + +```cpp +MATCHER(IsDivisibleBy7, "") { return (arg % 7) == 0; } +``` + +allows you to write + +```cpp + // Expects mock_foo.Bar(n) to be called where n is divisible by 7. + EXPECT_CALL(mock_foo, Bar(IsDivisibleBy7())); +``` + +or, + +```cpp + using ::testing::Not; + ... + // Verifies that a value is divisible by 7 and the other is not. + EXPECT_THAT(some_expression, IsDivisibleBy7()); + EXPECT_THAT(some_other_expression, Not(IsDivisibleBy7())); +``` + +If the above assertions fail, they will print something like: + +```shell + Value of: some_expression + Expected: is divisible by 7 + Actual: 27 + ... + Value of: some_other_expression + Expected: not (is divisible by 7) + Actual: 21 +``` + +where the descriptions `"is divisible by 7"` and `"not (is divisible by 7)"` are +automatically calculated from the matcher name `IsDivisibleBy7`. + +As you may have noticed, the auto-generated descriptions (especially those for +the negation) may not be so great. You can always override them with a `string` +expression of your own: + +```cpp +MATCHER(IsDivisibleBy7, + absl::StrCat(negation ? "isn't" : "is", " divisible by 7")) { + return (arg % 7) == 0; +} +``` + +Optionally, you can stream additional information to a hidden argument named +`result_listener` to explain the match result. For example, a better definition +of `IsDivisibleBy7` is: + +```cpp +MATCHER(IsDivisibleBy7, "") { + if ((arg % 7) == 0) + return true; + + *result_listener << "the remainder is " << (arg % 7); + return false; +} +``` + +With this definition, the above assertion will give a better message: + +```shell + Value of: some_expression + Expected: is divisible by 7 + Actual: 27 (the remainder is 6) +``` + +You should let `MatchAndExplain()` print *any additional information* that can +help a user understand the match result. Note that it should explain why the +match succeeds in case of a success (unless it's obvious) - this is useful when +the matcher is used inside `Not()`. There is no need to print the argument value +itself, as gMock already prints it for you. + +{: .callout .note} +NOTE: The type of the value being matched (`arg_type`) is determined by the +context in which you use the matcher and is supplied to you by the compiler, so +you don't need to worry about declaring it (nor can you). This allows the +matcher to be polymorphic. For example, `IsDivisibleBy7()` can be used to match +any type where the value of `(arg % 7) == 0` can be implicitly converted to a +`bool`. In the `Bar(IsDivisibleBy7())` example above, if method `Bar()` takes an +`int`, `arg_type` will be `int`; if it takes an `unsigned long`, `arg_type` will +be `unsigned long`; and so on. + +### Writing New Parameterized Matchers Quickly + +Sometimes you'll want to define a matcher that has parameters. For that you can +use the macro: + +```cpp +MATCHER_P(name, param_name, description_string) { statements; } +``` + +where the description string can be either `""` or a `string` expression that +references `negation` and `param_name`. + +For example: + +```cpp +MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +``` + +will allow you to write: + +```cpp + EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +``` + +which may lead to this message (assuming `n` is 10): + +```shell + Value of: Blah("a") + Expected: has absolute value 10 + Actual: -9 +``` + +Note that both the matcher description and its parameter are printed, making the +message human-friendly. + +In the matcher definition body, you can write `foo_type` to reference the type +of a parameter named `foo`. For example, in the body of +`MATCHER_P(HasAbsoluteValue, value)` above, you can write `value_type` to refer +to the type of `value`. + +gMock also provides `MATCHER_P2`, `MATCHER_P3`, ..., up to `MATCHER_P10` to +support multi-parameter matchers: + +```cpp +MATCHER_Pk(name, param_1, ..., param_k, description_string) { statements; } +``` + +Please note that the custom description string is for a particular *instance* of +the matcher, where the parameters have been bound to actual values. Therefore +usually you'll want the parameter values to be part of the description. gMock +lets you do that by referencing the matcher parameters in the description string +expression. + +For example, + +```cpp +using ::testing::PrintToString; +MATCHER_P2(InClosedRange, low, hi, + absl::StrFormat("%s in range [%s, %s]", negation ? "isn't" : "is", + PrintToString(low), PrintToString(hi))) { + return low <= arg && arg <= hi; +} +... +EXPECT_THAT(3, InClosedRange(4, 6)); +``` + +would generate a failure that contains the message: + +```shell + Expected: is in range [4, 6] +``` + +If you specify `""` as the description, the failure message will contain the +sequence of words in the matcher name followed by the parameter values printed +as a tuple. For example, + +```cpp + MATCHER_P2(InClosedRange, low, hi, "") { ... } + ... + EXPECT_THAT(3, InClosedRange(4, 6)); +``` + +would generate a failure that contains the text: + +```shell + Expected: in closed range (4, 6) +``` + +For the purpose of typing, you can view + +```cpp +MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +``` + +as shorthand for + +```cpp +template +FooMatcherPk +Foo(p1_type p1, ..., pk_type pk) { ... } +``` + +When you write `Foo(v1, ..., vk)`, the compiler infers the types of the +parameters `v1`, ..., and `vk` for you. If you are not happy with the result of +the type inference, you can specify the types by explicitly instantiating the +template, as in `Foo(5, false)`. As said earlier, you don't get to +(or need to) specify `arg_type` as that's determined by the context in which the +matcher is used. + +You can assign the result of expression `Foo(p1, ..., pk)` to a variable of type +`FooMatcherPk`. This can be useful when composing +matchers. Matchers that don't have a parameter or have only one parameter have +special types: you can assign `Foo()` to a `FooMatcher`-typed variable, and +assign `Foo(p)` to a `FooMatcherP`-typed variable. + +While you can instantiate a matcher template with reference types, passing the +parameters by pointer usually makes your code more readable. If, however, you +still want to pass a parameter by reference, be aware that in the failure +message generated by the matcher you will see the value of the referenced object +but not its address. + +You can overload matchers with different numbers of parameters: + +```cpp +MATCHER_P(Blah, a, description_string_1) { ... } +MATCHER_P2(Blah, a, b, description_string_2) { ... } +``` + +While it's tempting to always use the `MATCHER*` macros when defining a new +matcher, you should also consider implementing the matcher interface directly +instead (see the recipes that follow), especially if you need to use the matcher +a lot. While these approaches require more work, they give you more control on +the types of the value being matched and the matcher parameters, which in +general leads to better compiler error messages that pay off in the long run. +They also allow overloading matchers based on parameter types (as opposed to +just based on the number of parameters). + +### Writing New Monomorphic Matchers + +A matcher of argument type `T` implements the matcher interface for `T` and does +two things: it tests whether a value of type `T` matches the matcher, and can +describe what kind of values it matches. The latter ability is used for +generating readable error messages when expectations are violated. + +A matcher of `T` must declare a typedef like: + +```cpp +using is_gtest_matcher = void; +``` + +and supports the following operations: + +```cpp +// Match a value and optionally explain into an ostream. +bool matched = matcher.MatchAndExplain(value, maybe_os); +// where `value` is of type `T` and +// `maybe_os` is of type `std::ostream*`, where it can be null if the caller +// is not interested in there textual explanation. + +matcher.DescribeTo(os); +matcher.DescribeNegationTo(os); +// where `os` is of type `std::ostream*`. +``` + +If you need a custom matcher but `Truly()` is not a good option (for example, +you may not be happy with the way `Truly(predicate)` describes itself, or you +may want your matcher to be polymorphic as `Eq(value)` is), you can define a +matcher to do whatever you want in two steps: first implement the matcher +interface, and then define a factory function to create a matcher instance. The +second step is not strictly needed but it makes the syntax of using the matcher +nicer. + +For example, you can define a matcher to test whether an `int` is divisible by 7 +and then use it like this: + +```cpp +using ::testing::Matcher; + +class DivisibleBy7Matcher { + public: + using is_gtest_matcher = void; + + bool MatchAndExplain(int n, std::ostream*) const { + return (n % 7) == 0; + } + + void DescribeTo(std::ostream* os) const { + *os << "is divisible by 7"; + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "is not divisible by 7"; + } +}; + +Matcher DivisibleBy7() { + return DivisibleBy7Matcher(); +} + +... + EXPECT_CALL(foo, Bar(DivisibleBy7())); +``` + +You may improve the matcher message by streaming additional information to the +`os` argument in `MatchAndExplain()`: + +```cpp +class DivisibleBy7Matcher { + public: + bool MatchAndExplain(int n, std::ostream* os) const { + const int remainder = n % 7; + if (remainder != 0 && os != nullptr) { + *os << "the remainder is " << remainder; + } + return remainder == 0; + } + ... +}; +``` + +Then, `EXPECT_THAT(x, DivisibleBy7());` may generate a message like this: + +```shell +Value of: x +Expected: is divisible by 7 + Actual: 23 (the remainder is 2) +``` + +{: .callout .tip} +Tip: for convenience, `MatchAndExplain()` can take a `MatchResultListener*` +instead of `std::ostream*`. + +### Writing New Polymorphic Matchers + +Expanding what we learned above to *polymorphic* matchers is now just as simple +as adding templates in the right place. + +```cpp + +class NotNullMatcher { + public: + using is_gtest_matcher = void; + + // To implement a polymorphic matcher, we just need to make MatchAndExplain a + // template on its first argument. + + // In this example, we want to use NotNull() with any pointer, so + // MatchAndExplain() accepts a pointer of any type as its first argument. + // In general, you can define MatchAndExplain() as an ordinary method or + // a method template, or even overload it. + template + bool MatchAndExplain(T* p, std::ostream*) const { + return p != nullptr; + } + + // Describes the property of a value matching this matcher. + void DescribeTo(std::ostream* os) const { *os << "is not NULL"; } + + // Describes the property of a value NOT matching this matcher. + void DescribeNegationTo(std::ostream* os) const { *os << "is NULL"; } +}; + +NotNullMatcher NotNull() { + return NotNullMatcher(); +} + +... + + EXPECT_CALL(foo, Bar(NotNull())); // The argument must be a non-NULL pointer. +``` + +### Legacy Matcher Implementation + +Defining matchers used to be somewhat more complicated, in which it required +several supporting classes and virtual functions. To implement a matcher for +type `T` using the legacy API you have to derive from `MatcherInterface` and +call `MakeMatcher` to construct the object. + +The interface looks like this: + +```cpp +class MatchResultListener { + public: + ... + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template + MatchResultListener& operator<<(const T& x); + + // Returns the underlying ostream. + std::ostream* stream(); +}; + +template +class MatcherInterface { + public: + virtual ~MatcherInterface(); + + // Returns true if and only if the matcher matches x; also explains the match + // result to 'listener'. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Describes this matcher to an ostream. + virtual void DescribeTo(std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. + virtual void DescribeNegationTo(std::ostream* os) const; +}; +``` + +Fortunately, most of the time you can define a polymorphic matcher easily with +the help of `MakePolymorphicMatcher()`. Here's how you can define `NotNull()` as +an example: + +```cpp +using ::testing::MakePolymorphicMatcher; +using ::testing::MatchResultListener; +using ::testing::PolymorphicMatcher; + +class NotNullMatcher { + public: + // To implement a polymorphic matcher, first define a COPYABLE class + // that has three members MatchAndExplain(), DescribeTo(), and + // DescribeNegationTo(), like the following. + + // In this example, we want to use NotNull() with any pointer, so + // MatchAndExplain() accepts a pointer of any type as its first argument. + // In general, you can define MatchAndExplain() as an ordinary method or + // a method template, or even overload it. + template + bool MatchAndExplain(T* p, + MatchResultListener* /* listener */) const { + return p != NULL; + } + + // Describes the property of a value matching this matcher. + void DescribeTo(std::ostream* os) const { *os << "is not NULL"; } + + // Describes the property of a value NOT matching this matcher. + void DescribeNegationTo(std::ostream* os) const { *os << "is NULL"; } +}; + +// To construct a polymorphic matcher, pass an instance of the class +// to MakePolymorphicMatcher(). Note the return type. +PolymorphicMatcher NotNull() { + return MakePolymorphicMatcher(NotNullMatcher()); +} + +... + + EXPECT_CALL(foo, Bar(NotNull())); // The argument must be a non-NULL pointer. +``` + +{: .callout .note} +**Note:** Your polymorphic matcher class does **not** need to inherit from +`MatcherInterface` or any other class, and its methods do **not** need to be +virtual. + +Like in a monomorphic matcher, you may explain the match result by streaming +additional information to the `listener` argument in `MatchAndExplain()`. + +### Writing New Cardinalities + +A cardinality is used in `Times()` to tell gMock how many times you expect a +call to occur. It doesn't have to be exact. For example, you can say +`AtLeast(5)` or `Between(2, 4)`. + +If the [built-in set](gmock_cheat_sheet.md#CardinalityList) of cardinalities +doesn't suit you, you are free to define your own by implementing the following +interface (in namespace `testing`): + +```cpp +class CardinalityInterface { + public: + virtual ~CardinalityInterface(); + + // Returns true if and only if call_count calls will satisfy this cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true if and only if call_count calls will saturate this + // cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(std::ostream* os) const = 0; +}; +``` + +For example, to specify that a call must occur even number of times, you can +write + +```cpp +using ::testing::Cardinality; +using ::testing::CardinalityInterface; +using ::testing::MakeCardinality; + +class EvenNumberCardinality : public CardinalityInterface { + public: + bool IsSatisfiedByCallCount(int call_count) const override { + return (call_count % 2) == 0; + } + + bool IsSaturatedByCallCount(int call_count) const override { + return false; + } + + void DescribeTo(std::ostream* os) const { + *os << "called even number of times"; + } +}; + +Cardinality EvenNumber() { + return MakeCardinality(new EvenNumberCardinality); +} + +... + EXPECT_CALL(foo, Bar(3)) + .Times(EvenNumber()); +``` + +### Writing New Actions {#QuickNewActions} + +If the built-in actions don't work for you, you can easily define your own one. +All you need is a call operator with a signature compatible with the mocked +function. So you can use a lambda: + +```cpp +MockFunction mock; +EXPECT_CALL(mock, Call).WillOnce([](const int input) { return input * 7; }); +EXPECT_EQ(mock.AsStdFunction()(2), 14); +``` + +Or a struct with a call operator (even a templated one): + +```cpp +struct MultiplyBy { + template + T operator()(T arg) { return arg * multiplier; } + + int multiplier; +}; + +// Then use: +// EXPECT_CALL(...).WillOnce(MultiplyBy{7}); +``` + +It's also fine for the callable to take no arguments, ignoring the arguments +supplied to the mock function: + +```cpp +MockFunction mock; +EXPECT_CALL(mock, Call).WillOnce([] { return 17; }); +EXPECT_EQ(mock.AsStdFunction()(0), 17); +``` + +When used with `WillOnce`, the callable can assume it will be called at most +once and is allowed to be a move-only type: + +```cpp +// An action that contains move-only types and has an &&-qualified operator, +// demanding in the type system that it be called at most once. This can be +// used with WillOnce, but the compiler will reject it if handed to +// WillRepeatedly. +struct MoveOnlyAction { + std::unique_ptr move_only_state; + std::unique_ptr operator()() && { return std::move(move_only_state); } +}; + +MockFunction()> mock; +EXPECT_CALL(mock, Call).WillOnce(MoveOnlyAction{std::make_unique(17)}); +EXPECT_THAT(mock.AsStdFunction()(), Pointee(Eq(17))); +``` + +More generally, to use with a mock function whose signature is `R(Args...)` the +object can be anything convertible to `OnceAction` or +`Action. The difference between the two is that `OnceAction` has +weaker requirements (`Action` requires a copy-constructible input that can be +called repeatedly whereas `OnceAction` requires only move-constructible and +supports `&&`-qualified call operators), but can be used only with `WillOnce`. +`OnceAction` is typically relevant only when supporting move-only types or +actions that want a type-system guarantee that they will be called at most once. + +Typically the `OnceAction` and `Action` templates need not be referenced +directly in your actions: a struct or class with a call operator is sufficient, +as in the examples above. But fancier polymorphic actions that need to know the +specific return type of the mock function can define templated conversion +operators to make that possible. See `gmock-actions.h` for examples. + +#### Legacy macro-based Actions + +Before C++11, the functor-based actions were not supported; the old way of +writing actions was through a set of `ACTION*` macros. We suggest to avoid them +in new code; they hide a lot of logic behind the macro, potentially leading to +harder-to-understand compiler errors. Nevertheless, we cover them here for +completeness. + +By writing + +```cpp +ACTION(name) { statements; } +``` + +in a namespace scope (i.e. not inside a class or function), you will define an +action with the given name that executes the statements. The value returned by +`statements` will be used as the return value of the action. Inside the +statements, you can refer to the K-th (0-based) argument of the mock function as +`argK`. For example: + +```cpp +ACTION(IncrementArg1) { return ++(*arg1); } +``` + +allows you to write + +```cpp +... WillOnce(IncrementArg1()); +``` + +Note that you don't need to specify the types of the mock function arguments. +Rest assured that your code is type-safe though: you'll get a compiler error if +`*arg1` doesn't support the `++` operator, or if the type of `++(*arg1)` isn't +compatible with the mock function's return type. + +Another example: + +```cpp +ACTION(Foo) { + (*arg2)(5); + Blah(); + *arg1 = 0; + return arg0; +} +``` + +defines an action `Foo()` that invokes argument #2 (a function pointer) with 5, +calls function `Blah()`, sets the value pointed to by argument #1 to 0, and +returns argument #0. + +For more convenience and flexibility, you can also use the following pre-defined +symbols in the body of `ACTION`: + +`argK_type` | The type of the K-th (0-based) argument of the mock function +:-------------- | :----------------------------------------------------------- +`args` | All arguments of the mock function as a tuple +`args_type` | The type of all arguments of the mock function as a tuple +`return_type` | The return type of the mock function +`function_type` | The type of the mock function + +For example, when using an `ACTION` as a stub action for mock function: + +```cpp +int DoSomething(bool flag, int* ptr); +``` + +we have: + +Pre-defined Symbol | Is Bound To +------------------ | --------------------------------- +`arg0` | the value of `flag` +`arg0_type` | the type `bool` +`arg1` | the value of `ptr` +`arg1_type` | the type `int*` +`args` | the tuple `(flag, ptr)` +`args_type` | the type `std::tuple` +`return_type` | the type `int` +`function_type` | the type `int(bool, int*)` + +#### Legacy macro-based parameterized Actions + +Sometimes you'll want to parameterize an action you define. For that we have +another macro + +```cpp +ACTION_P(name, param) { statements; } +``` + +For example, + +```cpp +ACTION_P(Add, n) { return arg0 + n; } +``` + +will allow you to write + +```cpp +// Returns argument #0 + 5. +... WillOnce(Add(5)); +``` + +For convenience, we use the term *arguments* for the values used to invoke the +mock function, and the term *parameters* for the values used to instantiate an +action. + +Note that you don't need to provide the type of the parameter either. Suppose +the parameter is named `param`, you can also use the gMock-defined symbol +`param_type` to refer to the type of the parameter as inferred by the compiler. +For example, in the body of `ACTION_P(Add, n)` above, you can write `n_type` for +the type of `n`. + +gMock also provides `ACTION_P2`, `ACTION_P3`, and etc to support multi-parameter +actions. For example, + +```cpp +ACTION_P2(ReturnDistanceTo, x, y) { + double dx = arg0 - x; + double dy = arg1 - y; + return sqrt(dx*dx + dy*dy); +} +``` + +lets you write + +```cpp +... WillOnce(ReturnDistanceTo(5.0, 26.5)); +``` + +You can view `ACTION` as a degenerated parameterized action where the number of +parameters is 0. + +You can also easily define actions overloaded on the number of parameters: + +```cpp +ACTION_P(Plus, a) { ... } +ACTION_P2(Plus, a, b) { ... } +``` + +### Restricting the Type of an Argument or Parameter in an ACTION + +For maximum brevity and reusability, the `ACTION*` macros don't ask you to +provide the types of the mock function arguments and the action parameters. +Instead, we let the compiler infer the types for us. + +Sometimes, however, we may want to be more explicit about the types. There are +several tricks to do that. For example: + +```cpp +ACTION(Foo) { + // Makes sure arg0 can be converted to int. + int n = arg0; + ... use n instead of arg0 here ... +} + +ACTION_P(Bar, param) { + // Makes sure the type of arg1 is const char*. + ::testing::StaticAssertTypeEq(); + + // Makes sure param can be converted to bool. + bool flag = param; +} +``` + +where `StaticAssertTypeEq` is a compile-time assertion in googletest that +verifies two types are the same. + +### Writing New Action Templates Quickly + +Sometimes you want to give an action explicit template parameters that cannot be +inferred from its value parameters. `ACTION_TEMPLATE()` supports that and can be +viewed as an extension to `ACTION()` and `ACTION_P*()`. + +The syntax: + +```cpp +ACTION_TEMPLATE(ActionName, + HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), + AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +``` + +defines an action template that takes *m* explicit template parameters and *n* +value parameters, where *m* is in [1, 10] and *n* is in [0, 10]. `name_i` is the +name of the *i*-th template parameter, and `kind_i` specifies whether it's a +`typename`, an integral constant, or a template. `p_i` is the name of the *i*-th +value parameter. + +Example: + +```cpp +// DuplicateArg(output) converts the k-th argument of the mock +// function to type T and copies it to *output. +ACTION_TEMPLATE(DuplicateArg, + // Note the comma between int and k: + HAS_2_TEMPLATE_PARAMS(int, k, typename, T), + AND_1_VALUE_PARAMS(output)) { + *output = T(std::get(args)); +} +``` + +To create an instance of an action template, write: + +```cpp +ActionName(v1, ..., v_n) +``` + +where the `t`s are the template arguments and the `v`s are the value arguments. +The value argument types are inferred by the compiler. For example: + +```cpp +using ::testing::_; +... + int n; + EXPECT_CALL(mock, Foo).WillOnce(DuplicateArg<1, unsigned char>(&n)); +``` + +If you want to explicitly specify the value argument types, you can provide +additional template arguments: + +```cpp +ActionName(v1, ..., v_n) +``` + +where `u_i` is the desired type of `v_i`. + +`ACTION_TEMPLATE` and `ACTION`/`ACTION_P*` can be overloaded on the number of +value parameters, but not on the number of template parameters. Without the +restriction, the meaning of the following is unclear: + +```cpp + OverloadedAction(x); +``` + +Are we using a single-template-parameter action where `bool` refers to the type +of `x`, or a two-template-parameter action where the compiler is asked to infer +the type of `x`? + +### Using the ACTION Object's Type + +If you are writing a function that returns an `ACTION` object, you'll need to +know its type. The type depends on the macro used to define the action and the +parameter types. The rule is relatively simple: + + +| Given Definition | Expression | Has Type | +| ----------------------------- | ------------------- | --------------------- | +| `ACTION(Foo)` | `Foo()` | `FooAction` | +| `ACTION_TEMPLATE(Foo, HAS_m_TEMPLATE_PARAMS(...), AND_0_VALUE_PARAMS())` | `Foo()` | `FooAction` | +| `ACTION_P(Bar, param)` | `Bar(int_value)` | `BarActionP` | +| `ACTION_TEMPLATE(Bar, HAS_m_TEMPLATE_PARAMS(...), AND_1_VALUE_PARAMS(p1))` | `Bar(int_value)` | `BarActionP` | +| `ACTION_P2(Baz, p1, p2)` | `Baz(bool_value, int_value)` | `BazActionP2` | +| `ACTION_TEMPLATE(Baz, HAS_m_TEMPLATE_PARAMS(...), AND_2_VALUE_PARAMS(p1, p2))` | `Baz(bool_value, int_value)` | `BazActionP2` | +| ... | ... | ... | + + +Note that we have to pick different suffixes (`Action`, `ActionP`, `ActionP2`, +and etc) for actions with different numbers of value parameters, or the action +definitions cannot be overloaded on the number of them. + +### Writing New Monomorphic Actions {#NewMonoActions} + +While the `ACTION*` macros are very convenient, sometimes they are +inappropriate. For example, despite the tricks shown in the previous recipes, +they don't let you directly specify the types of the mock function arguments and +the action parameters, which in general leads to unoptimized compiler error +messages that can baffle unfamiliar users. They also don't allow overloading +actions based on parameter types without jumping through some hoops. + +An alternative to the `ACTION*` macros is to implement +`::testing::ActionInterface`, where `F` is the type of the mock function in +which the action will be used. For example: + +```cpp +template +class ActionInterface { + public: + virtual ~ActionInterface(); + + // Performs the action. Result is the return type of function type + // F, and ArgumentTuple is the tuple of arguments of F. + // + + // For example, if F is int(bool, const string&), then Result would + // be int, and ArgumentTuple would be std::tuple. + virtual Result Perform(const ArgumentTuple& args) = 0; +}; +``` + +```cpp +using ::testing::_; +using ::testing::Action; +using ::testing::ActionInterface; +using ::testing::MakeAction; + +typedef int IncrementMethod(int*); + +class IncrementArgumentAction : public ActionInterface { + public: + int Perform(const std::tuple& args) override { + int* p = std::get<0>(args); // Grabs the first argument. + return *p++; + } +}; + +Action IncrementArgument() { + return MakeAction(new IncrementArgumentAction); +} + +... + EXPECT_CALL(foo, Baz(_)) + .WillOnce(IncrementArgument()); + + int n = 5; + foo.Baz(&n); // Should return 5 and change n to 6. +``` + +### Writing New Polymorphic Actions {#NewPolyActions} + +The previous recipe showed you how to define your own action. This is all good, +except that you need to know the type of the function in which the action will +be used. Sometimes that can be a problem. For example, if you want to use the +action in functions with *different* types (e.g. like `Return()` and +`SetArgPointee()`). + +If an action can be used in several types of mock functions, we say it's +*polymorphic*. The `MakePolymorphicAction()` function template makes it easy to +define such an action: + +```cpp +namespace testing { +template +PolymorphicAction MakePolymorphicAction(const Impl& impl); +} // namespace testing +``` + +As an example, let's define an action that returns the second argument in the +mock function's argument list. The first step is to define an implementation +class: + +```cpp +class ReturnSecondArgumentAction { + public: + template + Result Perform(const ArgumentTuple& args) const { + // To get the i-th (0-based) argument, use std::get(args). + return std::get<1>(args); + } +}; +``` + +This implementation class does *not* need to inherit from any particular class. +What matters is that it must have a `Perform()` method template. This method +template takes the mock function's arguments as a tuple in a **single** +argument, and returns the result of the action. It can be either `const` or not, +but must be invocable with exactly one template argument, which is the result +type. In other words, you must be able to call `Perform(args)` where `R` is +the mock function's return type and `args` is its arguments in a tuple. + +Next, we use `MakePolymorphicAction()` to turn an instance of the implementation +class into the polymorphic action we need. It will be convenient to have a +wrapper for this: + +```cpp +using ::testing::MakePolymorphicAction; +using ::testing::PolymorphicAction; + +PolymorphicAction ReturnSecondArgument() { + return MakePolymorphicAction(ReturnSecondArgumentAction()); +} +``` + +Now, you can use this polymorphic action the same way you use the built-in ones: + +```cpp +using ::testing::_; + +class MockFoo : public Foo { + public: + MOCK_METHOD(int, DoThis, (bool flag, int n), (override)); + MOCK_METHOD(string, DoThat, (int x, const char* str1, const char* str2), + (override)); +}; + + ... + MockFoo foo; + EXPECT_CALL(foo, DoThis).WillOnce(ReturnSecondArgument()); + EXPECT_CALL(foo, DoThat).WillOnce(ReturnSecondArgument()); + ... + foo.DoThis(true, 5); // Will return 5. + foo.DoThat(1, "Hi", "Bye"); // Will return "Hi". +``` + +### Teaching gMock How to Print Your Values + +When an uninteresting or unexpected call occurs, gMock prints the argument +values and the stack trace to help you debug. Assertion macros like +`EXPECT_THAT` and `EXPECT_EQ` also print the values in question when the +assertion fails. gMock and googletest do this using googletest's user-extensible +value printer. + +This printer knows how to print built-in C++ types, native arrays, STL +containers, and any type that supports the `<<` operator. For other types, it +prints the raw bytes in the value and hopes that you the user can figure it out. +[The GoogleTest advanced guide](advanced.md#teaching-googletest-how-to-print-your-values) +explains how to extend the printer to do a better job at printing your +particular type than to dump the bytes. + +## Useful Mocks Created Using gMock + + + + +### Mock std::function {#MockFunction} + +`std::function` is a general function type introduced in C++11. It is a +preferred way of passing callbacks to new interfaces. Functions are copyable, +and are not usually passed around by pointer, which makes them tricky to mock. +But fear not - `MockFunction` can help you with that. + +`MockFunction` has a mock method `Call()` with the signature: + +```cpp + R Call(T1, ..., Tn); +``` + +It also has a `AsStdFunction()` method, which creates a `std::function` proxy +forwarding to Call: + +```cpp + std::function AsStdFunction(); +``` + +To use `MockFunction`, first create `MockFunction` object and set up +expectations on its `Call` method. Then pass proxy obtained from +`AsStdFunction()` to the code you are testing. For example: + +```cpp +TEST(FooTest, RunsCallbackWithBarArgument) { + // 1. Create a mock object. + MockFunction mock_function; + + // 2. Set expectations on Call() method. + EXPECT_CALL(mock_function, Call("bar")).WillOnce(Return(1)); + + // 3. Exercise code that uses std::function. + Foo(mock_function.AsStdFunction()); + // Foo's signature can be either of: + // void Foo(const std::function& fun); + // void Foo(std::function fun); + + // 4. All expectations will be verified when mock_function + // goes out of scope and is destroyed. +} +``` + +Remember that function objects created with `AsStdFunction()` are just +forwarders. If you create multiple of them, they will share the same set of +expectations. + +Although `std::function` supports unlimited number of arguments, `MockFunction` +implementation is limited to ten. If you ever hit that limit... well, your +callback has bigger problems than being mockable. :-) diff --git a/Engine/lib/assimp/contrib/googletest/docs/gmock_faq.md b/Engine/lib/assimp/contrib/googletest/docs/gmock_faq.md new file mode 100644 index 000000000..8f220bf7a --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/gmock_faq.md @@ -0,0 +1,390 @@ +# Legacy gMock FAQ + +### When I call a method on my mock object, the method for the real object is invoked instead. What's the problem? + +In order for a method to be mocked, it must be *virtual*, unless you use the +[high-perf dependency injection technique](gmock_cook_book.md#MockingNonVirtualMethods). + +### Can I mock a variadic function? + +You cannot mock a variadic function (i.e. a function taking ellipsis (`...`) +arguments) directly in gMock. + +The problem is that in general, there is *no way* for a mock object to know how +many arguments are passed to the variadic method, and what the arguments' types +are. Only the *author of the base class* knows the protocol, and we cannot look +into his or her head. + +Therefore, to mock such a function, the *user* must teach the mock object how to +figure out the number of arguments and their types. One way to do it is to +provide overloaded versions of the function. + +Ellipsis arguments are inherited from C and not really a C++ feature. They are +unsafe to use and don't work with arguments that have constructors or +destructors. Therefore we recommend to avoid them in C++ as much as possible. + +### MSVC gives me warning C4301 or C4373 when I define a mock method with a const parameter. Why? + +If you compile this using Microsoft Visual C++ 2005 SP1: + +```cpp +class Foo { + ... + virtual void Bar(const int i) = 0; +}; + +class MockFoo : public Foo { + ... + MOCK_METHOD(void, Bar, (const int i), (override)); +}; +``` + +You may get the following warning: + +```shell +warning C4301: 'MockFoo::Bar': overriding virtual function only differs from 'Foo::Bar' by const/volatile qualifier +``` + +This is a MSVC bug. The same code compiles fine with gcc, for example. If you +use Visual C++ 2008 SP1, you would get the warning: + +```shell +warning C4373: 'MockFoo::Bar': virtual function overrides 'Foo::Bar', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers +``` + +In C++, if you *declare* a function with a `const` parameter, the `const` +modifier is ignored. Therefore, the `Foo` base class above is equivalent to: + +```cpp +class Foo { + ... + virtual void Bar(int i) = 0; // int or const int? Makes no difference. +}; +``` + +In fact, you can *declare* `Bar()` with an `int` parameter, and define it with a +`const int` parameter. The compiler will still match them up. + +Since making a parameter `const` is meaningless in the method declaration, we +recommend to remove it in both `Foo` and `MockFoo`. That should workaround the +VC bug. + +Note that we are talking about the *top-level* `const` modifier here. If the +function parameter is passed by pointer or reference, declaring the pointee or +referee as `const` is still meaningful. For example, the following two +declarations are *not* equivalent: + +```cpp +void Bar(int* p); // Neither p nor *p is const. +void Bar(const int* p); // p is not const, but *p is. +``` + +### I can't figure out why gMock thinks my expectations are not satisfied. What should I do? + +You might want to run your test with `--gmock_verbose=info`. This flag lets +gMock print a trace of every mock function call it receives. By studying the +trace, you'll gain insights on why the expectations you set are not met. + +If you see the message "The mock function has no default action set, and its +return type has no default value set.", then try +[adding a default action](gmock_cheat_sheet.md#OnCall). Due to a known issue, +unexpected calls on mocks without default actions don't print out a detailed +comparison between the actual arguments and the expected arguments. + +### My program crashed and `ScopedMockLog` spit out tons of messages. Is it a gMock bug? + +gMock and `ScopedMockLog` are likely doing the right thing here. + +When a test crashes, the failure signal handler will try to log a lot of +information (the stack trace, and the address map, for example). The messages +are compounded if you have many threads with depth stacks. When `ScopedMockLog` +intercepts these messages and finds that they don't match any expectations, it +prints an error for each of them. + +You can learn to ignore the errors, or you can rewrite your expectations to make +your test more robust, for example, by adding something like: + +```cpp +using ::testing::AnyNumber; +using ::testing::Not; +... + // Ignores any log not done by us. + EXPECT_CALL(log, Log(_, Not(EndsWith("/my_file.cc")), _)) + .Times(AnyNumber()); +``` + +### How can I assert that a function is NEVER called? + +```cpp +using ::testing::_; +... + EXPECT_CALL(foo, Bar(_)) + .Times(0); +``` + +### I have a failed test where gMock tells me TWICE that a particular expectation is not satisfied. Isn't this redundant? + +When gMock detects a failure, it prints relevant information (the mock function +arguments, the state of relevant expectations, and etc) to help the user debug. +If another failure is detected, gMock will do the same, including printing the +state of relevant expectations. + +Sometimes an expectation's state didn't change between two failures, and you'll +see the same description of the state twice. They are however *not* redundant, +as they refer to *different points in time*. The fact they are the same *is* +interesting information. + +### I get a heapcheck failure when using a mock object, but using a real object is fine. What can be wrong? + +Does the class (hopefully a pure interface) you are mocking have a virtual +destructor? + +Whenever you derive from a base class, make sure its destructor is virtual. +Otherwise Bad Things will happen. Consider the following code: + +```cpp +class Base { + public: + // Not virtual, but should be. + ~Base() { ... } + ... +}; + +class Derived : public Base { + public: + ... + private: + std::string value_; +}; + +... + Base* p = new Derived; + ... + delete p; // Surprise! ~Base() will be called, but ~Derived() will not + // - value_ is leaked. +``` + +By changing `~Base()` to virtual, `~Derived()` will be correctly called when +`delete p` is executed, and the heap checker will be happy. + +### The "newer expectations override older ones" rule makes writing expectations awkward. Why does gMock do that? + +When people complain about this, often they are referring to code like: + +```cpp +using ::testing::Return; +... + // foo.Bar() should be called twice, return 1 the first time, and return + // 2 the second time. However, I have to write the expectations in the + // reverse order. This sucks big time!!! + EXPECT_CALL(foo, Bar()) + .WillOnce(Return(2)) + .RetiresOnSaturation(); + EXPECT_CALL(foo, Bar()) + .WillOnce(Return(1)) + .RetiresOnSaturation(); +``` + +The problem, is that they didn't pick the **best** way to express the test's +intent. + +By default, expectations don't have to be matched in *any* particular order. If +you want them to match in a certain order, you need to be explicit. This is +gMock's (and jMock's) fundamental philosophy: it's easy to accidentally +over-specify your tests, and we want to make it harder to do so. + +There are two better ways to write the test spec. You could either put the +expectations in sequence: + +```cpp +using ::testing::Return; +... + // foo.Bar() should be called twice, return 1 the first time, and return + // 2 the second time. Using a sequence, we can write the expectations + // in their natural order. + { + InSequence s; + EXPECT_CALL(foo, Bar()) + .WillOnce(Return(1)) + .RetiresOnSaturation(); + EXPECT_CALL(foo, Bar()) + .WillOnce(Return(2)) + .RetiresOnSaturation(); + } +``` + +or you can put the sequence of actions in the same expectation: + +```cpp +using ::testing::Return; +... + // foo.Bar() should be called twice, return 1 the first time, and return + // 2 the second time. + EXPECT_CALL(foo, Bar()) + .WillOnce(Return(1)) + .WillOnce(Return(2)) + .RetiresOnSaturation(); +``` + +Back to the original questions: why does gMock search the expectations (and +`ON_CALL`s) from back to front? Because this allows a user to set up a mock's +behavior for the common case early (e.g. in the mock's constructor or the test +fixture's set-up phase) and customize it with more specific rules later. If +gMock searches from front to back, this very useful pattern won't be possible. + +### gMock prints a warning when a function without EXPECT_CALL is called, even if I have set its behavior using ON_CALL. Would it be reasonable not to show the warning in this case? + +When choosing between being neat and being safe, we lean toward the latter. So +the answer is that we think it's better to show the warning. + +Often people write `ON_CALL`s in the mock object's constructor or `SetUp()`, as +the default behavior rarely changes from test to test. Then in the test body +they set the expectations, which are often different for each test. Having an +`ON_CALL` in the set-up part of a test doesn't mean that the calls are expected. +If there's no `EXPECT_CALL` and the method is called, it's possibly an error. If +we quietly let the call go through without notifying the user, bugs may creep in +unnoticed. + +If, however, you are sure that the calls are OK, you can write + +```cpp +using ::testing::_; +... + EXPECT_CALL(foo, Bar(_)) + .WillRepeatedly(...); +``` + +instead of + +```cpp +using ::testing::_; +... + ON_CALL(foo, Bar(_)) + .WillByDefault(...); +``` + +This tells gMock that you do expect the calls and no warning should be printed. + +Also, you can control the verbosity by specifying `--gmock_verbose=error`. Other +values are `info` and `warning`. If you find the output too noisy when +debugging, just choose a less verbose level. + +### How can I delete the mock function's argument in an action? + +If your mock function takes a pointer argument and you want to delete that +argument, you can use testing::DeleteArg() to delete the N'th (zero-indexed) +argument: + +```cpp +using ::testing::_; + ... + MOCK_METHOD(void, Bar, (X* x, const Y& y)); + ... + EXPECT_CALL(mock_foo_, Bar(_, _)) + .WillOnce(testing::DeleteArg<0>())); +``` + +### How can I perform an arbitrary action on a mock function's argument? + +If you find yourself needing to perform some action that's not supported by +gMock directly, remember that you can define your own actions using +[`MakeAction()`](#NewMonoActions) or +[`MakePolymorphicAction()`](#NewPolyActions), or you can write a stub function +and invoke it using [`Invoke()`](#FunctionsAsActions). + +```cpp +using ::testing::_; +using ::testing::Invoke; + ... + MOCK_METHOD(void, Bar, (X* p)); + ... + EXPECT_CALL(mock_foo_, Bar(_)) + .WillOnce(Invoke(MyAction(...))); +``` + +### My code calls a static/global function. Can I mock it? + +You can, but you need to make some changes. + +In general, if you find yourself needing to mock a static function, it's a sign +that your modules are too tightly coupled (and less flexible, less reusable, +less testable, etc). You are probably better off defining a small interface and +call the function through that interface, which then can be easily mocked. It's +a bit of work initially, but usually pays for itself quickly. + +This Google Testing Blog +[post](https://testing.googleblog.com/2008/06/defeat-static-cling.html) says it +excellently. Check it out. + +### My mock object needs to do complex stuff. It's a lot of pain to specify the actions. gMock sucks! + +I know it's not a question, but you get an answer for free any way. :-) + +With gMock, you can create mocks in C++ easily. And people might be tempted to +use them everywhere. Sometimes they work great, and sometimes you may find them, +well, a pain to use. So, what's wrong in the latter case? + +When you write a test without using mocks, you exercise the code and assert that +it returns the correct value or that the system is in an expected state. This is +sometimes called "state-based testing". + +Mocks are great for what some call "interaction-based" testing: instead of +checking the system state at the very end, mock objects verify that they are +invoked the right way and report an error as soon as it arises, giving you a +handle on the precise context in which the error was triggered. This is often +more effective and economical to do than state-based testing. + +If you are doing state-based testing and using a test double just to simulate +the real object, you are probably better off using a fake. Using a mock in this +case causes pain, as it's not a strong point for mocks to perform complex +actions. If you experience this and think that mocks suck, you are just not +using the right tool for your problem. Or, you might be trying to solve the +wrong problem. :-) + +### I got a warning "Uninteresting function call encountered - default action taken.." Should I panic? + +By all means, NO! It's just an FYI. :-) + +What it means is that you have a mock function, you haven't set any expectations +on it (by gMock's rule this means that you are not interested in calls to this +function and therefore it can be called any number of times), and it is called. +That's OK - you didn't say it's not OK to call the function! + +What if you actually meant to disallow this function to be called, but forgot to +write `EXPECT_CALL(foo, Bar()).Times(0)`? While one can argue that it's the +user's fault, gMock tries to be nice and prints you a note. + +So, when you see the message and believe that there shouldn't be any +uninteresting calls, you should investigate what's going on. To make your life +easier, gMock dumps the stack trace when an uninteresting call is encountered. +From that you can figure out which mock function it is, and how it is called. + +### I want to define a custom action. Should I use Invoke() or implement the ActionInterface interface? + +Either way is fine - you want to choose the one that's more convenient for your +circumstance. + +Usually, if your action is for a particular function type, defining it using +`Invoke()` should be easier; if your action can be used in functions of +different types (e.g. if you are defining `Return(*value*)`), +`MakePolymorphicAction()` is easiest. Sometimes you want precise control on what +types of functions the action can be used in, and implementing `ActionInterface` +is the way to go here. See the implementation of `Return()` in `gmock-actions.h` +for an example. + +### I use SetArgPointee() in WillOnce(), but gcc complains about "conflicting return type specified". What does it mean? + +You got this error as gMock has no idea what value it should return when the +mock method is called. `SetArgPointee()` says what the side effect is, but +doesn't say what the return value should be. You need `DoAll()` to chain a +`SetArgPointee()` with a `Return()` that provides a value appropriate to the API +being mocked. + +See this [recipe](gmock_cook_book.md#mocking-side-effects) for more details and +an example. + +### I have a huge mock class, and Microsoft Visual C++ runs out of memory when compiling it. What can I do? + +We've noticed that when the `/clr` compiler flag is used, Visual C++ uses 5~6 +times as much memory when compiling a mock class. We suggest to avoid `/clr` +when compiling native C++ mocks. diff --git a/Engine/lib/assimp/contrib/googletest/docs/gmock_for_dummies.md b/Engine/lib/assimp/contrib/googletest/docs/gmock_for_dummies.md new file mode 100644 index 000000000..43f907aaa --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/gmock_for_dummies.md @@ -0,0 +1,700 @@ +# gMock for Dummies + +## What Is gMock? + +When you write a prototype or test, often it's not feasible or wise to rely on +real objects entirely. A **mock object** implements the same interface as a real +object (so it can be used as one), but lets you specify at run time how it will +be used and what it should do (which methods will be called? in which order? how +many times? with what arguments? what will they return? etc). + +It is easy to confuse the term *fake objects* with mock objects. Fakes and mocks +actually mean very different things in the Test-Driven Development (TDD) +community: + +* **Fake** objects have working implementations, but usually take some + shortcut (perhaps to make the operations less expensive), which makes them + not suitable for production. An in-memory file system would be an example of + a fake. +* **Mocks** are objects pre-programmed with *expectations*, which form a + specification of the calls they are expected to receive. + +If all this seems too abstract for you, don't worry - the most important thing +to remember is that a mock allows you to check the *interaction* between itself +and code that uses it. The difference between fakes and mocks shall become much +clearer once you start to use mocks. + +**gMock** is a library (sometimes we also call it a "framework" to make it sound +cool) for creating mock classes and using them. It does to C++ what +jMock/EasyMock does to Java (well, more or less). + +When using gMock, + +1. first, you use some simple macros to describe the interface you want to + mock, and they will expand to the implementation of your mock class; +2. next, you create some mock objects and specify its expectations and behavior + using an intuitive syntax; +3. then you exercise code that uses the mock objects. gMock will catch any + violation to the expectations as soon as it arises. + +## Why gMock? + +While mock objects help you remove unnecessary dependencies in tests and make +them fast and reliable, using mocks manually in C++ is *hard*: + +* Someone has to implement the mocks. The job is usually tedious and + error-prone. No wonder people go great distance to avoid it. +* The quality of those manually written mocks is a bit, uh, unpredictable. You + may see some really polished ones, but you may also see some that were + hacked up in a hurry and have all sorts of ad hoc restrictions. +* The knowledge you gained from using one mock doesn't transfer to the next + one. + +In contrast, Java and Python programmers have some fine mock frameworks (jMock, +EasyMock, etc), which automate the creation of mocks. As a result, mocking is a +proven effective technique and widely adopted practice in those communities. +Having the right tool absolutely makes the difference. + +gMock was built to help C++ programmers. It was inspired by jMock and EasyMock, +but designed with C++'s specifics in mind. It is your friend if any of the +following problems is bothering you: + +* You are stuck with a sub-optimal design and wish you had done more + prototyping before it was too late, but prototyping in C++ is by no means + "rapid". +* Your tests are slow as they depend on too many libraries or use expensive + resources (e.g. a database). +* Your tests are brittle as some resources they use are unreliable (e.g. the + network). +* You want to test how your code handles a failure (e.g. a file checksum + error), but it's not easy to cause one. +* You need to make sure that your module interacts with other modules in the + right way, but it's hard to observe the interaction; therefore you resort to + observing the side effects at the end of the action, but it's awkward at + best. +* You want to "mock out" your dependencies, except that they don't have mock + implementations yet; and, frankly, you aren't thrilled by some of those + hand-written mocks. + +We encourage you to use gMock as + +* a *design* tool, for it lets you experiment with your interface design early + and often. More iterations lead to better designs! +* a *testing* tool to cut your tests' outbound dependencies and probe the + interaction between your module and its collaborators. + +## Getting Started + +gMock is bundled with googletest. + +## A Case for Mock Turtles + +Let's look at an example. Suppose you are developing a graphics program that +relies on a [LOGO](http://en.wikipedia.org/wiki/Logo_programming_language)-like +API for drawing. How would you test that it does the right thing? Well, you can +run it and compare the screen with a golden screen snapshot, but let's admit it: +tests like this are expensive to run and fragile (What if you just upgraded to a +shiny new graphics card that has better anti-aliasing? Suddenly you have to +update all your golden images.). It would be too painful if all your tests are +like this. Fortunately, you learned about +[Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) and know the right thing +to do: instead of having your application talk to the system API directly, wrap +the API in an interface (say, `Turtle`) and code to that interface: + +```cpp +class Turtle { + ... + virtual ~Turtle() {} + virtual void PenUp() = 0; + virtual void PenDown() = 0; + virtual void Forward(int distance) = 0; + virtual void Turn(int degrees) = 0; + virtual void GoTo(int x, int y) = 0; + virtual int GetX() const = 0; + virtual int GetY() const = 0; +}; +``` + +(Note that the destructor of `Turtle` **must** be virtual, as is the case for +**all** classes you intend to inherit from - otherwise the destructor of the +derived class will not be called when you delete an object through a base +pointer, and you'll get corrupted program states like memory leaks.) + +You can control whether the turtle's movement will leave a trace using `PenUp()` +and `PenDown()`, and control its movement using `Forward()`, `Turn()`, and +`GoTo()`. Finally, `GetX()` and `GetY()` tell you the current position of the +turtle. + +Your program will normally use a real implementation of this interface. In +tests, you can use a mock implementation instead. This allows you to easily +check what drawing primitives your program is calling, with what arguments, and +in which order. Tests written this way are much more robust (they won't break +because your new machine does anti-aliasing differently), easier to read and +maintain (the intent of a test is expressed in the code, not in some binary +images), and run *much, much faster*. + +## Writing the Mock Class + +If you are lucky, the mocks you need to use have already been implemented by +some nice people. If, however, you find yourself in the position to write a mock +class, relax - gMock turns this task into a fun game! (Well, almost.) + +### How to Define It + +Using the `Turtle` interface as example, here are the simple steps you need to +follow: + +* Derive a class `MockTurtle` from `Turtle`. +* Take a *virtual* function of `Turtle` (while it's possible to + [mock non-virtual methods using templates](gmock_cook_book.md#MockingNonVirtualMethods), + it's much more involved). +* In the `public:` section of the child class, write `MOCK_METHOD();` +* Now comes the fun part: you take the function signature, cut-and-paste it + into the macro, and add two commas - one between the return type and the + name, another between the name and the argument list. +* If you're mocking a const method, add a 4th parameter containing `(const)` + (the parentheses are required). +* Since you're overriding a virtual method, we suggest adding the `override` + keyword. For const methods the 4th parameter becomes `(const, override)`, + for non-const methods just `(override)`. This isn't mandatory. +* Repeat until all virtual functions you want to mock are done. (It goes + without saying that *all* pure virtual methods in your abstract class must + be either mocked or overridden.) + +After the process, you should have something like: + +```cpp +#include // Brings in gMock. + +class MockTurtle : public Turtle { + public: + ... + MOCK_METHOD(void, PenUp, (), (override)); + MOCK_METHOD(void, PenDown, (), (override)); + MOCK_METHOD(void, Forward, (int distance), (override)); + MOCK_METHOD(void, Turn, (int degrees), (override)); + MOCK_METHOD(void, GoTo, (int x, int y), (override)); + MOCK_METHOD(int, GetX, (), (const, override)); + MOCK_METHOD(int, GetY, (), (const, override)); +}; +``` + +You don't need to define these mock methods somewhere else - the `MOCK_METHOD` +macro will generate the definitions for you. It's that simple! + +### Where to Put It + +When you define a mock class, you need to decide where to put its definition. +Some people put it in a `_test.cc`. This is fine when the interface being mocked +(say, `Foo`) is owned by the same person or team. Otherwise, when the owner of +`Foo` changes it, your test could break. (You can't really expect `Foo`'s +maintainer to fix every test that uses `Foo`, can you?) + +Generally, you should not mock classes you don't own. If you must mock such a +class owned by others, define the mock class in `Foo`'s Bazel package (usually +the same directory or a `testing` sub-directory), and put it in a `.h` and a +`cc_library` with `testonly=True`. Then everyone can reference them from their +tests. If `Foo` ever changes, there is only one copy of `MockFoo` to change, and +only tests that depend on the changed methods need to be fixed. + +Another way to do it: you can introduce a thin layer `FooAdaptor` on top of +`Foo` and code to this new interface. Since you own `FooAdaptor`, you can absorb +changes in `Foo` much more easily. While this is more work initially, carefully +choosing the adaptor interface can make your code easier to write and more +readable (a net win in the long run), as you can choose `FooAdaptor` to fit your +specific domain much better than `Foo` does. + +## Using Mocks in Tests + +Once you have a mock class, using it is easy. The typical work flow is: + +1. Import the gMock names from the `testing` namespace such that you can use + them unqualified (You only have to do it once per file). Remember that + namespaces are a good idea. +2. Create some mock objects. +3. Specify your expectations on them (How many times will a method be called? + With what arguments? What should it do? etc.). +4. Exercise some code that uses the mocks; optionally, check the result using + googletest assertions. If a mock method is called more than expected or with + wrong arguments, you'll get an error immediately. +5. When a mock is destructed, gMock will automatically check whether all + expectations on it have been satisfied. + +Here's an example: + +```cpp +#include "path/to/mock-turtle.h" +#include +#include + +using ::testing::AtLeast; // #1 + +TEST(PainterTest, CanDrawSomething) { + MockTurtle turtle; // #2 + EXPECT_CALL(turtle, PenDown()) // #3 + .Times(AtLeast(1)); + + Painter painter(&turtle); // #4 + + EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5 +} +``` + +As you might have guessed, this test checks that `PenDown()` is called at least +once. If the `painter` object didn't call this method, your test will fail with +a message like this: + +```text +path/to/my_test.cc:119: Failure +Actual function call count doesn't match this expectation: +Actually: never called; +Expected: called at least once. +Stack trace: +... +``` + +**Tip 1:** If you run the test from an Emacs buffer, you can hit `` on +the line number to jump right to the failed expectation. + +**Tip 2:** If your mock objects are never deleted, the final verification won't +happen. Therefore it's a good idea to turn on the heap checker in your tests +when you allocate mocks on the heap. You get that automatically if you use the +`gtest_main` library already. + +**Important note:** gMock requires expectations to be set **before** the mock +functions are called, otherwise the behavior is **undefined**. Do not alternate +between calls to `EXPECT_CALL()` and calls to the mock functions, and do not set +any expectations on a mock after passing the mock to an API. + +This means `EXPECT_CALL()` should be read as expecting that a call will occur +*in the future*, not that a call has occurred. Why does gMock work like that? +Well, specifying the expectation beforehand allows gMock to report a violation +as soon as it rises, when the context (stack trace, etc) is still available. +This makes debugging much easier. + +Admittedly, this test is contrived and doesn't do much. You can easily achieve +the same effect without using gMock. However, as we shall reveal soon, gMock +allows you to do *so much more* with the mocks. + +## Setting Expectations + +The key to using a mock object successfully is to set the *right expectations* +on it. If you set the expectations too strict, your test will fail as the result +of unrelated changes. If you set them too loose, bugs can slip through. You want +to do it just right such that your test can catch exactly the kind of bugs you +intend it to catch. gMock provides the necessary means for you to do it "just +right." + +### General Syntax + +In gMock we use the `EXPECT_CALL()` macro to set an expectation on a mock +method. The general syntax is: + +```cpp +EXPECT_CALL(mock_object, method(matchers)) + .Times(cardinality) + .WillOnce(action) + .WillRepeatedly(action); +``` + +The macro has two arguments: first the mock object, and then the method and its +arguments. Note that the two are separated by a comma (`,`), not a period (`.`). +(Why using a comma? The answer is that it was necessary for technical reasons.) +If the method is not overloaded, the macro can also be called without matchers: + +```cpp +EXPECT_CALL(mock_object, non-overloaded-method) + .Times(cardinality) + .WillOnce(action) + .WillRepeatedly(action); +``` + +This syntax allows the test writer to specify "called with any arguments" +without explicitly specifying the number or types of arguments. To avoid +unintended ambiguity, this syntax may only be used for methods that are not +overloaded. + +Either form of the macro can be followed by some optional *clauses* that provide +more information about the expectation. We'll discuss how each clause works in +the coming sections. + +This syntax is designed to make an expectation read like English. For example, +you can probably guess that + +```cpp +using ::testing::Return; +... +EXPECT_CALL(turtle, GetX()) + .Times(5) + .WillOnce(Return(100)) + .WillOnce(Return(150)) + .WillRepeatedly(Return(200)); +``` + +says that the `turtle` object's `GetX()` method will be called five times, it +will return 100 the first time, 150 the second time, and then 200 every time. +Some people like to call this style of syntax a Domain-Specific Language (DSL). + +{: .callout .note} +**Note:** Why do we use a macro to do this? Well it serves two purposes: first +it makes expectations easily identifiable (either by `grep` or by a human +reader), and second it allows gMock to include the source file location of a +failed expectation in messages, making debugging easier. + +### Matchers: What Arguments Do We Expect? + +When a mock function takes arguments, we may specify what arguments we are +expecting, for example: + +```cpp +// Expects the turtle to move forward by 100 units. +EXPECT_CALL(turtle, Forward(100)); +``` + +Oftentimes you do not want to be too specific. Remember that talk about tests +being too rigid? Over specification leads to brittle tests and obscures the +intent of tests. Therefore we encourage you to specify only what's necessary—no +more, no less. If you aren't interested in the value of an argument, write `_` +as the argument, which means "anything goes": + +```cpp +using ::testing::_; +... +// Expects that the turtle jumps to somewhere on the x=50 line. +EXPECT_CALL(turtle, GoTo(50, _)); +``` + +`_` is an instance of what we call **matchers**. A matcher is like a predicate +and can test whether an argument is what we'd expect. You can use a matcher +inside `EXPECT_CALL()` wherever a function argument is expected. `_` is a +convenient way of saying "any value". + +In the above examples, `100` and `50` are also matchers; implicitly, they are +the same as `Eq(100)` and `Eq(50)`, which specify that the argument must be +equal (using `operator==`) to the matcher argument. There are many +[built-in matchers](reference/matchers.md) for common types (as well as +[custom matchers](gmock_cook_book.md#NewMatchers)); for example: + +```cpp +using ::testing::Ge; +... +// Expects the turtle moves forward by at least 100. +EXPECT_CALL(turtle, Forward(Ge(100))); +``` + +If you don't care about *any* arguments, rather than specify `_` for each of +them you may instead omit the parameter list: + +```cpp +// Expects the turtle to move forward. +EXPECT_CALL(turtle, Forward); +// Expects the turtle to jump somewhere. +EXPECT_CALL(turtle, GoTo); +``` + +This works for all non-overloaded methods; if a method is overloaded, you need +to help gMock resolve which overload is expected by specifying the number of +arguments and possibly also the +[types of the arguments](gmock_cook_book.md#SelectOverload). + +### Cardinalities: How Many Times Will It Be Called? + +The first clause we can specify following an `EXPECT_CALL()` is `Times()`. We +call its argument a **cardinality** as it tells *how many times* the call should +occur. It allows us to repeat an expectation many times without actually writing +it as many times. More importantly, a cardinality can be "fuzzy", just like a +matcher can be. This allows a user to express the intent of a test exactly. + +An interesting special case is when we say `Times(0)`. You may have guessed - it +means that the function shouldn't be called with the given arguments at all, and +gMock will report a googletest failure whenever the function is (wrongfully) +called. + +We've seen `AtLeast(n)` as an example of fuzzy cardinalities earlier. For the +list of built-in cardinalities you can use, see +[here](gmock_cheat_sheet.md#CardinalityList). + +The `Times()` clause can be omitted. **If you omit `Times()`, gMock will infer +the cardinality for you.** The rules are easy to remember: + +* If **neither** `WillOnce()` **nor** `WillRepeatedly()` is in the + `EXPECT_CALL()`, the inferred cardinality is `Times(1)`. +* If there are *n* `WillOnce()`'s but **no** `WillRepeatedly()`, where *n* >= + 1, the cardinality is `Times(n)`. +* If there are *n* `WillOnce()`'s and **one** `WillRepeatedly()`, where *n* >= + 0, the cardinality is `Times(AtLeast(n))`. + +**Quick quiz:** what do you think will happen if a function is expected to be +called twice but actually called four times? + +### Actions: What Should It Do? + +Remember that a mock object doesn't really have a working implementation? We as +users have to tell it what to do when a method is invoked. This is easy in +gMock. + +First, if the return type of a mock function is a built-in type or a pointer, +the function has a **default action** (a `void` function will just return, a +`bool` function will return `false`, and other functions will return 0). In +addition, in C++ 11 and above, a mock function whose return type is +default-constructible (i.e. has a default constructor) has a default action of +returning a default-constructed value. If you don't say anything, this behavior +will be used. + +Second, if a mock function doesn't have a default action, or the default action +doesn't suit you, you can specify the action to be taken each time the +expectation matches using a series of `WillOnce()` clauses followed by an +optional `WillRepeatedly()`. For example, + +```cpp +using ::testing::Return; +... +EXPECT_CALL(turtle, GetX()) + .WillOnce(Return(100)) + .WillOnce(Return(200)) + .WillOnce(Return(300)); +``` + +says that `turtle.GetX()` will be called *exactly three times* (gMock inferred +this from how many `WillOnce()` clauses we've written, since we didn't +explicitly write `Times()`), and will return 100, 200, and 300 respectively. + +```cpp +using ::testing::Return; +... +EXPECT_CALL(turtle, GetY()) + .WillOnce(Return(100)) + .WillOnce(Return(200)) + .WillRepeatedly(Return(300)); +``` + +says that `turtle.GetY()` will be called *at least twice* (gMock knows this as +we've written two `WillOnce()` clauses and a `WillRepeatedly()` while having no +explicit `Times()`), will return 100 and 200 respectively the first two times, +and 300 from the third time on. + +Of course, if you explicitly write a `Times()`, gMock will not try to infer the +cardinality itself. What if the number you specified is larger than there are +`WillOnce()` clauses? Well, after all `WillOnce()`s are used up, gMock will do +the *default* action for the function every time (unless, of course, you have a +`WillRepeatedly()`.). + +What can we do inside `WillOnce()` besides `Return()`? You can return a +reference using `ReturnRef(`*`variable`*`)`, or invoke a pre-defined function, +among [others](gmock_cook_book.md#using-actions). + +**Important note:** The `EXPECT_CALL()` statement evaluates the action clause +only once, even though the action may be performed many times. Therefore you +must be careful about side effects. The following may not do what you want: + +```cpp +using ::testing::Return; +... +int n = 100; +EXPECT_CALL(turtle, GetX()) + .Times(4) + .WillRepeatedly(Return(n++)); +``` + +Instead of returning 100, 101, 102, ..., consecutively, this mock function will +always return 100 as `n++` is only evaluated once. Similarly, `Return(new Foo)` +will create a new `Foo` object when the `EXPECT_CALL()` is executed, and will +return the same pointer every time. If you want the side effect to happen every +time, you need to define a custom action, which we'll teach in the +[cook book](gmock_cook_book.md). + +Time for another quiz! What do you think the following means? + +```cpp +using ::testing::Return; +... +EXPECT_CALL(turtle, GetY()) + .Times(4) + .WillOnce(Return(100)); +``` + +Obviously `turtle.GetY()` is expected to be called four times. But if you think +it will return 100 every time, think twice! Remember that one `WillOnce()` +clause will be consumed each time the function is invoked and the default action +will be taken afterwards. So the right answer is that `turtle.GetY()` will +return 100 the first time, but **return 0 from the second time on**, as +returning 0 is the default action for `int` functions. + +### Using Multiple Expectations {#MultiExpectations} + +So far we've only shown examples where you have a single expectation. More +realistically, you'll specify expectations on multiple mock methods which may be +from multiple mock objects. + +By default, when a mock method is invoked, gMock will search the expectations in +the **reverse order** they are defined, and stop when an active expectation that +matches the arguments is found (you can think of it as "newer rules override +older ones."). If the matching expectation cannot take any more calls, you will +get an upper-bound-violated failure. Here's an example: + +```cpp +using ::testing::_; +... +EXPECT_CALL(turtle, Forward(_)); // #1 +EXPECT_CALL(turtle, Forward(10)) // #2 + .Times(2); +``` + +If `Forward(10)` is called three times in a row, the third time it will be an +error, as the last matching expectation (#2) has been saturated. If, however, +the third `Forward(10)` call is replaced by `Forward(20)`, then it would be OK, +as now #1 will be the matching expectation. + +{: .callout .note} +**Note:** Why does gMock search for a match in the *reverse* order of the +expectations? The reason is that this allows a user to set up the default +expectations in a mock object's constructor or the test fixture's set-up phase +and then customize the mock by writing more specific expectations in the test +body. So, if you have two expectations on the same method, you want to put the +one with more specific matchers **after** the other, or the more specific rule +would be shadowed by the more general one that comes after it. + +{: .callout .tip} +**Tip:** It is very common to start with a catch-all expectation for a method +and `Times(AnyNumber())` (omitting arguments, or with `_` for all arguments, if +overloaded). This makes any calls to the method expected. This is not necessary +for methods that are not mentioned at all (these are "uninteresting"), but is +useful for methods that have some expectations, but for which other calls are +ok. See +[Understanding Uninteresting vs Unexpected Calls](gmock_cook_book.md#uninteresting-vs-unexpected). + +### Ordered vs Unordered Calls {#OrderedCalls} + +By default, an expectation can match a call even though an earlier expectation +hasn't been satisfied. In other words, the calls don't have to occur in the +order the expectations are specified. + +Sometimes, you may want all the expected calls to occur in a strict order. To +say this in gMock is easy: + +```cpp +using ::testing::InSequence; +... +TEST(FooTest, DrawsLineSegment) { + ... + { + InSequence seq; + + EXPECT_CALL(turtle, PenDown()); + EXPECT_CALL(turtle, Forward(100)); + EXPECT_CALL(turtle, PenUp()); + } + Foo(); +} +``` + +By creating an object of type `InSequence`, all expectations in its scope are +put into a *sequence* and have to occur *sequentially*. Since we are just +relying on the constructor and destructor of this object to do the actual work, +its name is really irrelevant. + +In this example, we test that `Foo()` calls the three expected functions in the +order as written. If a call is made out-of-order, it will be an error. + +(What if you care about the relative order of some of the calls, but not all of +them? Can you specify an arbitrary partial order? The answer is ... yes! The +details can be found [here](gmock_cook_book.md#OrderedCalls).) + +### All Expectations Are Sticky (Unless Said Otherwise) {#StickyExpectations} + +Now let's do a quick quiz to see how well you can use this mock stuff already. +How would you test that the turtle is asked to go to the origin *exactly twice* +(you want to ignore any other instructions it receives)? + +After you've come up with your answer, take a look at ours and compare notes +(solve it yourself first - don't cheat!): + +```cpp +using ::testing::_; +using ::testing::AnyNumber; +... +EXPECT_CALL(turtle, GoTo(_, _)) // #1 + .Times(AnyNumber()); +EXPECT_CALL(turtle, GoTo(0, 0)) // #2 + .Times(2); +``` + +Suppose `turtle.GoTo(0, 0)` is called three times. In the third time, gMock will +see that the arguments match expectation #2 (remember that we always pick the +last matching expectation). Now, since we said that there should be only two +such calls, gMock will report an error immediately. This is basically what we've +told you in the [Using Multiple Expectations](#MultiExpectations) section above. + +This example shows that **expectations in gMock are "sticky" by default**, in +the sense that they remain active even after we have reached their invocation +upper bounds. This is an important rule to remember, as it affects the meaning +of the spec, and is **different** to how it's done in many other mocking +frameworks (Why'd we do that? Because we think our rule makes the common cases +easier to express and understand.). + +Simple? Let's see if you've really understood it: what does the following code +say? + +```cpp +using ::testing::Return; +... +for (int i = n; i > 0; i--) { + EXPECT_CALL(turtle, GetX()) + .WillOnce(Return(10*i)); +} +``` + +If you think it says that `turtle.GetX()` will be called `n` times and will +return 10, 20, 30, ..., consecutively, think twice! The problem is that, as we +said, expectations are sticky. So, the second time `turtle.GetX()` is called, +the last (latest) `EXPECT_CALL()` statement will match, and will immediately +lead to an "upper bound violated" error - this piece of code is not very useful! + +One correct way of saying that `turtle.GetX()` will return 10, 20, 30, ..., is +to explicitly say that the expectations are *not* sticky. In other words, they +should *retire* as soon as they are saturated: + +```cpp +using ::testing::Return; +... +for (int i = n; i > 0; i--) { + EXPECT_CALL(turtle, GetX()) + .WillOnce(Return(10*i)) + .RetiresOnSaturation(); +} +``` + +And, there's a better way to do it: in this case, we expect the calls to occur +in a specific order, and we line up the actions to match the order. Since the +order is important here, we should make it explicit using a sequence: + +```cpp +using ::testing::InSequence; +using ::testing::Return; +... +{ + InSequence s; + + for (int i = 1; i <= n; i++) { + EXPECT_CALL(turtle, GetX()) + .WillOnce(Return(10*i)) + .RetiresOnSaturation(); + } +} +``` + +By the way, the other situation where an expectation may *not* be sticky is when +it's in a sequence - as soon as another expectation that comes after it in the +sequence has been used, it automatically retires (and will never be used to +match any call). + +### Uninteresting Calls + +A mock object may have many methods, and not all of them are that interesting. +For example, in some tests we may not care about how many times `GetX()` and +`GetY()` get called. + +In gMock, if you are not interested in a method, just don't say anything about +it. If a call to this method occurs, you'll see a warning in the test output, +but it won't be a failure. This is called "naggy" behavior; to change, see +[The Nice, the Strict, and the Naggy](gmock_cook_book.md#NiceStrictNaggy). diff --git a/Engine/lib/assimp/contrib/googletest/docs/index.md b/Engine/lib/assimp/contrib/googletest/docs/index.md new file mode 100644 index 000000000..b162c7401 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/index.md @@ -0,0 +1,22 @@ +# GoogleTest User's Guide + +## Welcome to GoogleTest! + +GoogleTest is Google's C++ testing and mocking framework. This user's guide has +the following contents: + +* [GoogleTest Primer](primer.md) - Teaches you how to write simple tests using + GoogleTest. Read this first if you are new to GoogleTest. +* [GoogleTest Advanced](advanced.md) - Read this when you've finished the + Primer and want to utilize GoogleTest to its full potential. +* [GoogleTest Samples](samples.md) - Describes some GoogleTest samples. +* [GoogleTest FAQ](faq.md) - Have a question? Want some tips? Check here + first. +* [Mocking for Dummies](gmock_for_dummies.md) - Teaches you how to create mock + objects and use them in tests. +* [Mocking Cookbook](gmock_cook_book.md) - Includes tips and approaches to + common mocking use cases. +* [Mocking Cheat Sheet](gmock_cheat_sheet.md) - A handy reference for + matchers, actions, invariants, and more. +* [Mocking FAQ](gmock_faq.md) - Contains answers to some mocking-specific + questions. diff --git a/Engine/lib/assimp/contrib/googletest/docs/pkgconfig.md b/Engine/lib/assimp/contrib/googletest/docs/pkgconfig.md new file mode 100644 index 000000000..bf05d5931 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/pkgconfig.md @@ -0,0 +1,144 @@ +## Using GoogleTest from various build systems + +GoogleTest comes with pkg-config files that can be used to determine all +necessary flags for compiling and linking to GoogleTest (and GoogleMock). +Pkg-config is a standardised plain-text format containing + +* the includedir (-I) path +* necessary macro (-D) definitions +* further required flags (-pthread) +* the library (-L) path +* the library (-l) to link to + +All current build systems support pkg-config in one way or another. For all +examples here we assume you want to compile the sample +`samples/sample3_unittest.cc`. + +### CMake + +Using `pkg-config` in CMake is fairly easy: + +```cmake +find_package(PkgConfig) +pkg_search_module(GTEST REQUIRED gtest_main) + +add_executable(testapp) +target_sources(testapp PRIVATE samples/sample3_unittest.cc) +target_link_libraries(testapp PRIVATE ${GTEST_LDFLAGS}) +target_compile_options(testapp PRIVATE ${GTEST_CFLAGS}) + +enable_testing() +add_test(first_and_only_test testapp) +``` + +It is generally recommended that you use `target_compile_options` + `_CFLAGS` +over `target_include_directories` + `_INCLUDE_DIRS` as the former includes not +just -I flags (GoogleTest might require a macro indicating to internal headers +that all libraries have been compiled with threading enabled. In addition, +GoogleTest might also require `-pthread` in the compiling step, and as such +splitting the pkg-config `Cflags` variable into include dirs and macros for +`target_compile_definitions()` might still miss this). The same recommendation +goes for using `_LDFLAGS` over the more commonplace `_LIBRARIES`, which happens +to discard `-L` flags and `-pthread`. + +### Help! pkg-config can't find GoogleTest! + +Let's say you have a `CMakeLists.txt` along the lines of the one in this +tutorial and you try to run `cmake`. It is very possible that you get a failure +along the lines of: + +``` +-- Checking for one of the modules 'gtest_main' +CMake Error at /usr/share/cmake/Modules/FindPkgConfig.cmake:640 (message): + None of the required 'gtest_main' found +``` + +These failures are common if you installed GoogleTest yourself and have not +sourced it from a distro or other package manager. If so, you need to tell +pkg-config where it can find the `.pc` files containing the information. Say you +installed GoogleTest to `/usr/local`, then it might be that the `.pc` files are +installed under `/usr/local/lib64/pkgconfig`. If you set + +``` +export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig +``` + +pkg-config will also try to look in `PKG_CONFIG_PATH` to find `gtest_main.pc`. + +### Using pkg-config in a cross-compilation setting + +Pkg-config can be used in a cross-compilation setting too. To do this, let's +assume the final prefix of the cross-compiled installation will be `/usr`, and +your sysroot is `/home/MYUSER/sysroot`. Configure and install GTest using + +``` +mkdir build && cmake -DCMAKE_INSTALL_PREFIX=/usr .. +``` + +Install into the sysroot using `DESTDIR`: + +``` +make -j install DESTDIR=/home/MYUSER/sysroot +``` + +Before we continue, it is recommended to **always** define the following two +variables for pkg-config in a cross-compilation setting: + +``` +export PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=yes +export PKG_CONFIG_ALLOW_SYSTEM_LIBS=yes +``` + +otherwise `pkg-config` will filter `-I` and `-L` flags against standard prefixes +such as `/usr` (see https://bugs.freedesktop.org/show_bug.cgi?id=28264#c3 for +reasons why this stripping needs to occur usually). + +If you look at the generated pkg-config file, it will look something like + +``` +libdir=/usr/lib64 +includedir=/usr/include + +Name: gtest +Description: GoogleTest (without main() function) +Version: 1.11.0 +URL: https://github.com/google/googletest +Libs: -L${libdir} -lgtest -lpthread +Cflags: -I${includedir} -DGTEST_HAS_PTHREAD=1 -lpthread +``` + +Notice that the sysroot is not included in `libdir` and `includedir`! If you try +to run `pkg-config` with the correct +`PKG_CONFIG_LIBDIR=/home/MYUSER/sysroot/usr/lib64/pkgconfig` against this `.pc` +file, you will get + +``` +$ pkg-config --cflags gtest +-DGTEST_HAS_PTHREAD=1 -lpthread -I/usr/include +$ pkg-config --libs gtest +-L/usr/lib64 -lgtest -lpthread +``` + +which is obviously wrong and points to the `CBUILD` and not `CHOST` root. In +order to use this in a cross-compilation setting, we need to tell pkg-config to +inject the actual sysroot into `-I` and `-L` variables. Let us now tell +pkg-config about the actual sysroot + +``` +export PKG_CONFIG_DIR= +export PKG_CONFIG_SYSROOT_DIR=/home/MYUSER/sysroot +export PKG_CONFIG_LIBDIR=${PKG_CONFIG_SYSROOT_DIR}/usr/lib64/pkgconfig +``` + +and running `pkg-config` again we get + +``` +$ pkg-config --cflags gtest +-DGTEST_HAS_PTHREAD=1 -lpthread -I/home/MYUSER/sysroot/usr/include +$ pkg-config --libs gtest +-L/home/MYUSER/sysroot/usr/lib64 -lgtest -lpthread +``` + +which contains the correct sysroot now. For a more comprehensive guide to also +including `${CHOST}` in build system calls, see the excellent tutorial by Diego +Elio Pettenò: diff --git a/Engine/lib/assimp/contrib/googletest/docs/platforms.md b/Engine/lib/assimp/contrib/googletest/docs/platforms.md new file mode 100644 index 000000000..d35a7be05 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/platforms.md @@ -0,0 +1,8 @@ +# Supported Platforms + +GoogleTest follows Google's +[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support). +See +[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md) +for a list of currently supported versions compilers, platforms, and build +tools. diff --git a/Engine/lib/assimp/contrib/googletest/docs/primer.md b/Engine/lib/assimp/contrib/googletest/docs/primer.md new file mode 100644 index 000000000..f2a97a726 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/primer.md @@ -0,0 +1,483 @@ +# GoogleTest Primer + +## Introduction: Why GoogleTest? + +*GoogleTest* helps you write better C++ tests. + +GoogleTest is a testing framework developed by the Testing Technology team with +Google's specific requirements and constraints in mind. Whether you work on +Linux, Windows, or a Mac, if you write C++ code, GoogleTest can help you. And it +supports *any* kind of tests, not just unit tests. + +So what makes a good test, and how does GoogleTest fit in? We believe: + +1. Tests should be *independent* and *repeatable*. It's a pain to debug a test + that succeeds or fails as a result of other tests. GoogleTest isolates the + tests by running each of them on a different object. When a test fails, + GoogleTest allows you to run it in isolation for quick debugging. +2. Tests should be well *organized* and reflect the structure of the tested + code. GoogleTest groups related tests into test suites that can share data + and subroutines. This common pattern is easy to recognize and makes tests + easy to maintain. Such consistency is especially helpful when people switch + projects and start to work on a new code base. +3. Tests should be *portable* and *reusable*. Google has a lot of code that is + platform-neutral; its tests should also be platform-neutral. GoogleTest + works on different OSes, with different compilers, with or without + exceptions, so GoogleTest tests can work with a variety of configurations. +4. When tests fail, they should provide as much *information* about the problem + as possible. GoogleTest doesn't stop at the first test failure. Instead, it + only stops the current test and continues with the next. You can also set up + tests that report non-fatal failures after which the current test continues. + Thus, you can detect and fix multiple bugs in a single run-edit-compile + cycle. +5. The testing framework should liberate test writers from housekeeping chores + and let them focus on the test *content*. GoogleTest automatically keeps + track of all tests defined, and doesn't require the user to enumerate them + in order to run them. +6. Tests should be *fast*. With GoogleTest, you can reuse shared resources + across tests and pay for the set-up/tear-down only once, without making + tests depend on each other. + +Since GoogleTest is based on the popular xUnit architecture, you'll feel right +at home if you've used JUnit or PyUnit before. If not, it will take you about 10 +minutes to learn the basics and get started. So let's go! + +## Beware of the Nomenclature + +{: .callout .note} +*Note:* There might be some confusion arising from different definitions of the +terms *Test*, *Test Case* and *Test Suite*, so beware of misunderstanding these. + +Historically, GoogleTest started to use the term *Test Case* for grouping +related tests, whereas current publications, including International Software +Testing Qualifications Board ([ISTQB](http://www.istqb.org/)) materials and +various textbooks on software quality, use the term +*[Test Suite][istqb test suite]* for this. + +The related term *Test*, as it is used in GoogleTest, corresponds to the term +*[Test Case][istqb test case]* of ISTQB and others. + +The term *Test* is commonly of broad enough sense, including ISTQB's definition +of *Test Case*, so it's not much of a problem here. But the term *Test Case* as +was used in Google Test is of contradictory sense and thus confusing. + +GoogleTest recently started replacing the term *Test Case* with *Test Suite*. +The preferred API is *TestSuite*. The older TestCase API is being slowly +deprecated and refactored away. + +So please be aware of the different definitions of the terms: + + +Meaning | GoogleTest Term | [ISTQB](http://www.istqb.org/) Term +:----------------------------------------------------------------------------------- | :---------------------- | :---------------------------------- +Exercise a particular program path with specific input values and verify the results | [TEST()](#simple-tests) | [Test Case][istqb test case] + + +[istqb test case]: http://glossary.istqb.org/en/search/test%20case +[istqb test suite]: http://glossary.istqb.org/en/search/test%20suite + +## Basic Concepts + +When using GoogleTest, you start by writing *assertions*, which are statements +that check whether a condition is true. An assertion's result can be *success*, +*nonfatal failure*, or *fatal failure*. If a fatal failure occurs, it aborts the +current function; otherwise the program continues normally. + +*Tests* use assertions to verify the tested code's behavior. If a test crashes +or has a failed assertion, then it *fails*; otherwise it *succeeds*. + +A *test suite* contains one or many tests. You should group your tests into test +suites that reflect the structure of the tested code. When multiple tests in a +test suite need to share common objects and subroutines, you can put them into a +*test fixture* class. + +A *test program* can contain multiple test suites. + +We'll now explain how to write a test program, starting at the individual +assertion level and building up to tests and test suites. + +## Assertions + +GoogleTest assertions are macros that resemble function calls. You test a class +or function by making assertions about its behavior. When an assertion fails, +GoogleTest prints the assertion's source file and line number location, along +with a failure message. You may also supply a custom failure message which will +be appended to GoogleTest's message. + +The assertions come in pairs that test the same thing but have different effects +on the current function. `ASSERT_*` versions generate fatal failures when they +fail, and **abort the current function**. `EXPECT_*` versions generate nonfatal +failures, which don't abort the current function. Usually `EXPECT_*` are +preferred, as they allow more than one failure to be reported in a test. +However, you should use `ASSERT_*` if it doesn't make sense to continue when the +assertion in question fails. + +Since a failed `ASSERT_*` returns from the current function immediately, +possibly skipping clean-up code that comes after it, it may cause a space leak. +Depending on the nature of the leak, it may or may not be worth fixing - so keep +this in mind if you get a heap checker error in addition to assertion errors. + +To provide a custom failure message, simply stream it into the macro using the +`<<` operator or a sequence of such operators. See the following example, using +the [`ASSERT_EQ` and `EXPECT_EQ`](reference/assertions.md#EXPECT_EQ) macros to +verify value equality: + +```c++ +ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; + +for (int i = 0; i < x.size(); ++i) { + EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i; +} +``` + +Anything that can be streamed to an `ostream` can be streamed to an assertion +macro--in particular, C strings and `string` objects. If a wide string +(`wchar_t*`, `TCHAR*` in `UNICODE` mode on Windows, or `std::wstring`) is +streamed to an assertion, it will be translated to UTF-8 when printed. + +GoogleTest provides a collection of assertions for verifying the behavior of +your code in various ways. You can check Boolean conditions, compare values +based on relational operators, verify string values, floating-point values, and +much more. There are even assertions that enable you to verify more complex +states by providing custom predicates. For the complete list of assertions +provided by GoogleTest, see the [Assertions Reference](reference/assertions.md). + +## Simple Tests + +To create a test: + +1. Use the `TEST()` macro to define and name a test function. These are + ordinary C++ functions that don't return a value. +2. In this function, along with any valid C++ statements you want to include, + use the various GoogleTest assertions to check values. +3. The test's result is determined by the assertions; if any assertion in the + test fails (either fatally or non-fatally), or if the test crashes, the + entire test fails. Otherwise, it succeeds. + +```c++ +TEST(TestSuiteName, TestName) { + ... test body ... +} +``` + +`TEST()` arguments go from general to specific. The *first* argument is the name +of the test suite, and the *second* argument is the test's name within the test +suite. Both names must be valid C++ identifiers, and they should not contain any +underscores (`_`). A test's *full name* consists of its containing test suite +and its individual name. Tests from different test suites can have the same +individual name. + +For example, let's take a simple integer function: + +```c++ +int Factorial(int n); // Returns the factorial of n +``` + +A test suite for this function might look like: + +```c++ +// Tests factorial of 0. +TEST(FactorialTest, HandlesZeroInput) { + EXPECT_EQ(Factorial(0), 1); +} + +// Tests factorial of positive numbers. +TEST(FactorialTest, HandlesPositiveInput) { + EXPECT_EQ(Factorial(1), 1); + EXPECT_EQ(Factorial(2), 2); + EXPECT_EQ(Factorial(3), 6); + EXPECT_EQ(Factorial(8), 40320); +} +``` + +GoogleTest groups the test results by test suites, so logically related tests +should be in the same test suite; in other words, the first argument to their +`TEST()` should be the same. In the above example, we have two tests, +`HandlesZeroInput` and `HandlesPositiveInput`, that belong to the same test +suite `FactorialTest`. + +When naming your test suites and tests, you should follow the same convention as +for +[naming functions and classes](https://google.github.io/styleguide/cppguide.html#Function_Names). + +**Availability**: Linux, Windows, Mac. + +## Test Fixtures: Using the Same Data Configuration for Multiple Tests {#same-data-multiple-tests} + +If you find yourself writing two or more tests that operate on similar data, you +can use a *test fixture*. This allows you to reuse the same configuration of +objects for several different tests. + +To create a fixture: + +1. Derive a class from `::testing::Test` . Start its body with `protected:`, as + we'll want to access fixture members from sub-classes. +2. Inside the class, declare any objects you plan to use. +3. If necessary, write a default constructor or `SetUp()` function to prepare + the objects for each test. A common mistake is to spell `SetUp()` as + **`Setup()`** with a small `u` - Use `override` in C++11 to make sure you + spelled it correctly. +4. If necessary, write a destructor or `TearDown()` function to release any + resources you allocated in `SetUp()` . To learn when you should use the + constructor/destructor and when you should use `SetUp()/TearDown()`, read + the [FAQ](faq.md#CtorVsSetUp). +5. If needed, define subroutines for your tests to share. + +When using a fixture, use `TEST_F()` instead of `TEST()` as it allows you to +access objects and subroutines in the test fixture: + +```c++ +TEST_F(TestFixtureClassName, TestName) { + ... test body ... +} +``` + +Unlike `TEST()`, in `TEST_F()` the first argument must be the name of the test +fixture class. (`_F` stands for "Fixture"). No test suite name is specified for +this macro. + +Unfortunately, the C++ macro system does not allow us to create a single macro +that can handle both types of tests. Using the wrong macro causes a compiler +error. + +Also, you must first define a test fixture class before using it in a +`TEST_F()`, or you'll get the compiler error "`virtual outside class +declaration`". + +For each test defined with `TEST_F()`, GoogleTest will create a *fresh* test +fixture at runtime, immediately initialize it via `SetUp()`, run the test, clean +up by calling `TearDown()`, and then delete the test fixture. Note that +different tests in the same test suite have different test fixture objects, and +GoogleTest always deletes a test fixture before it creates the next one. +GoogleTest does **not** reuse the same test fixture for multiple tests. Any +changes one test makes to the fixture do not affect other tests. + +As an example, let's write tests for a FIFO queue class named `Queue`, which has +the following interface: + +```c++ +template // E is the element type. +class Queue { + public: + Queue(); + void Enqueue(const E& element); + E* Dequeue(); // Returns NULL if the queue is empty. + size_t size() const; + ... +}; +``` + +First, define a fixture class. By convention, you should give it the name +`FooTest` where `Foo` is the class being tested. + +```c++ +class QueueTest : public ::testing::Test { + protected: + void SetUp() override { + // q0_ remains empty + q1_.Enqueue(1); + q2_.Enqueue(2); + q2_.Enqueue(3); + } + + // void TearDown() override {} + + Queue q0_; + Queue q1_; + Queue q2_; +}; +``` + +In this case, `TearDown()` is not needed since we don't have to clean up after +each test, other than what's already done by the destructor. + +Now we'll write tests using `TEST_F()` and this fixture. + +```c++ +TEST_F(QueueTest, IsEmptyInitially) { + EXPECT_EQ(q0_.size(), 0); +} + +TEST_F(QueueTest, DequeueWorks) { + int* n = q0_.Dequeue(); + EXPECT_EQ(n, nullptr); + + n = q1_.Dequeue(); + ASSERT_NE(n, nullptr); + EXPECT_EQ(*n, 1); + EXPECT_EQ(q1_.size(), 0); + delete n; + + n = q2_.Dequeue(); + ASSERT_NE(n, nullptr); + EXPECT_EQ(*n, 2); + EXPECT_EQ(q2_.size(), 1); + delete n; +} +``` + +The above uses both `ASSERT_*` and `EXPECT_*` assertions. The rule of thumb is +to use `EXPECT_*` when you want the test to continue to reveal more errors after +the assertion failure, and use `ASSERT_*` when continuing after failure doesn't +make sense. For example, the second assertion in the `Dequeue` test is +`ASSERT_NE(n, nullptr)`, as we need to dereference the pointer `n` later, which +would lead to a segfault when `n` is `NULL`. + +When these tests run, the following happens: + +1. GoogleTest constructs a `QueueTest` object (let's call it `t1`). +2. `t1.SetUp()` initializes `t1`. +3. The first test (`IsEmptyInitially`) runs on `t1`. +4. `t1.TearDown()` cleans up after the test finishes. +5. `t1` is destructed. +6. The above steps are repeated on another `QueueTest` object, this time + running the `DequeueWorks` test. + +**Availability**: Linux, Windows, Mac. + +## Invoking the Tests + +`TEST()` and `TEST_F()` implicitly register their tests with GoogleTest. So, +unlike with many other C++ testing frameworks, you don't have to re-list all +your defined tests in order to run them. + +After defining your tests, you can run them with `RUN_ALL_TESTS()`, which +returns `0` if all the tests are successful, or `1` otherwise. Note that +`RUN_ALL_TESTS()` runs *all tests* in your link unit--they can be from different +test suites, or even different source files. + +When invoked, the `RUN_ALL_TESTS()` macro: + +* Saves the state of all GoogleTest flags. + +* Creates a test fixture object for the first test. + +* Initializes it via `SetUp()`. + +* Runs the test on the fixture object. + +* Cleans up the fixture via `TearDown()`. + +* Deletes the fixture. + +* Restores the state of all GoogleTest flags. + +* Repeats the above steps for the next test, until all tests have run. + +If a fatal failure happens the subsequent steps will be skipped. + +{: .callout .important} +> IMPORTANT: You must **not** ignore the return value of `RUN_ALL_TESTS()`, or +> you will get a compiler error. The rationale for this design is that the +> automated testing service determines whether a test has passed based on its +> exit code, not on its stdout/stderr output; thus your `main()` function must +> return the value of `RUN_ALL_TESTS()`. +> +> Also, you should call `RUN_ALL_TESTS()` only **once**. Calling it more than +> once conflicts with some advanced GoogleTest features (e.g., thread-safe +> [death tests](advanced.md#death-tests)) and thus is not supported. + +**Availability**: Linux, Windows, Mac. + +## Writing the main() Function + +Most users should *not* need to write their own `main` function and instead link +with `gtest_main` (as opposed to with `gtest`), which defines a suitable entry +point. See the end of this section for details. The remainder of this section +should only apply when you need to do something custom before the tests run that +cannot be expressed within the framework of fixtures and test suites. + +If you write your own `main` function, it should return the value of +`RUN_ALL_TESTS()`. + +You can start from this boilerplate: + +```c++ +#include "this/package/foo.h" + +#include + +namespace my { +namespace project { +namespace { + +// The fixture for testing class Foo. +class FooTest : public ::testing::Test { + protected: + // You can remove any or all of the following functions if their bodies would + // be empty. + + FooTest() { + // You can do set-up work for each test here. + } + + ~FooTest() override { + // You can do clean-up work that doesn't throw exceptions here. + } + + // If the constructor and destructor are not enough for setting up + // and cleaning up each test, you can define the following methods: + + void SetUp() override { + // Code here will be called immediately after the constructor (right + // before each test). + } + + void TearDown() override { + // Code here will be called immediately after each test (right + // before the destructor). + } + + // Class members declared here can be used by all tests in the test suite + // for Foo. +}; + +// Tests that the Foo::Bar() method does Abc. +TEST_F(FooTest, MethodBarDoesAbc) { + const std::string input_filepath = "this/package/testdata/myinputfile.dat"; + const std::string output_filepath = "this/package/testdata/myoutputfile.dat"; + Foo f; + EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0); +} + +// Tests that Foo does Xyz. +TEST_F(FooTest, DoesXyz) { + // Exercises the Xyz feature of Foo. +} + +} // namespace +} // namespace project +} // namespace my + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +``` + +The `::testing::InitGoogleTest()` function parses the command line for +GoogleTest flags, and removes all recognized flags. This allows the user to +control a test program's behavior via various flags, which we'll cover in the +[AdvancedGuide](advanced.md). You **must** call this function before calling +`RUN_ALL_TESTS()`, or the flags won't be properly initialized. + +On Windows, `InitGoogleTest()` also works with wide strings, so it can be used +in programs compiled in `UNICODE` mode as well. + +But maybe you think that writing all those `main` functions is too much work? We +agree with you completely, and that's why Google Test provides a basic +implementation of main(). If it fits your needs, then just link your test with +the `gtest_main` library and you are good to go. + +{: .callout .note} +NOTE: `ParseGUnitFlags()` is deprecated in favor of `InitGoogleTest()`. + +## Known Limitations + +* Google Test is designed to be thread-safe. The implementation is thread-safe + on systems where the `pthreads` library is available. It is currently + *unsafe* to use Google Test assertions from two threads concurrently on + other systems (e.g. Windows). In most tests this is not an issue as usually + the assertions are done in the main thread. If you want to help, you can + volunteer to implement the necessary synchronization primitives in + `gtest-port.h` for your platform. diff --git a/Engine/lib/assimp/contrib/googletest/docs/quickstart-bazel.md b/Engine/lib/assimp/contrib/googletest/docs/quickstart-bazel.md new file mode 100644 index 000000000..4f693dbe7 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/quickstart-bazel.md @@ -0,0 +1,153 @@ +# Quickstart: Building with Bazel + +This tutorial aims to get you up and running with GoogleTest using the Bazel +build system. If you're using GoogleTest for the first time or need a refresher, +we recommend this tutorial as a starting point. + +## Prerequisites + +To complete this tutorial, you'll need: + +* A compatible operating system (e.g. Linux, macOS, Windows). +* A compatible C++ compiler that supports at least C++14. +* [Bazel](https://bazel.build/), the preferred build system used by the + GoogleTest team. + +See [Supported Platforms](platforms.md) for more information about platforms +compatible with GoogleTest. + +If you don't already have Bazel installed, see the +[Bazel installation guide](https://bazel.build/install). + +{: .callout .note} Note: The terminal commands in this tutorial show a Unix +shell prompt, but the commands work on the Windows command line as well. + +## Set up a Bazel workspace + +A +[Bazel workspace](https://docs.bazel.build/versions/main/build-ref.html#workspace) +is a directory on your filesystem that you use to manage source files for the +software you want to build. Each workspace directory has a text file named +`WORKSPACE` which may be empty, or may contain references to external +dependencies required to build the outputs. + +First, create a directory for your workspace: + +``` +$ mkdir my_workspace && cd my_workspace +``` + +Next, you’ll create the `WORKSPACE` file to specify dependencies. A common and +recommended way to depend on GoogleTest is to use a +[Bazel external dependency](https://docs.bazel.build/versions/main/external.html) +via the +[`http_archive` rule](https://docs.bazel.build/versions/main/repo/http.html#http_archive). +To do this, in the root directory of your workspace (`my_workspace/`), create a +file named `WORKSPACE` with the following contents: + +``` +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "com_google_googletest", + urls = ["https://github.com/google/googletest/archive/5ab508a01f9eb089207ee87fd547d290da39d015.zip"], + strip_prefix = "googletest-5ab508a01f9eb089207ee87fd547d290da39d015", +) +``` + +The above configuration declares a dependency on GoogleTest which is downloaded +as a ZIP archive from GitHub. In the above example, +`5ab508a01f9eb089207ee87fd547d290da39d015` is the Git commit hash of the +GoogleTest version to use; we recommend updating the hash often to point to the +latest version. Use a recent hash on the `main` branch. + +Now you're ready to build C++ code that uses GoogleTest. + +## Create and run a binary + +With your Bazel workspace set up, you can now use GoogleTest code within your +own project. + +As an example, create a file named `hello_test.cc` in your `my_workspace` +directory with the following contents: + +```cpp +#include + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} +``` + +GoogleTest provides [assertions](primer.md#assertions) that you use to test the +behavior of your code. The above sample includes the main GoogleTest header file +and demonstrates some basic assertions. + +To build the code, create a file named `BUILD` in the same directory with the +following contents: + +``` +cc_test( + name = "hello_test", + size = "small", + srcs = ["hello_test.cc"], + deps = ["@com_google_googletest//:gtest_main"], +) +``` + +This `cc_test` rule declares the C++ test binary you want to build, and links to +GoogleTest (`//:gtest_main`) using the prefix you specified in the `WORKSPACE` +file (`@com_google_googletest`). For more information about Bazel `BUILD` files, +see the +[Bazel C++ Tutorial](https://docs.bazel.build/versions/main/tutorial/cpp.html). + +{: .callout .note} +NOTE: In the example below, we assume Clang or GCC and set `--cxxopt=-std=c++14` +to ensure that GoogleTest is compiled as C++14 instead of the compiler's default +setting (which could be C++11). For MSVC, the equivalent would be +`--cxxopt=/std:c++14`. See [Supported Platforms](platforms.md) for more details +on supported language versions. + +Now you can build and run your test: + +
+my_workspace$ bazel test --cxxopt=-std=c++14 --test_output=all //:hello_test
+INFO: Analyzed target //:hello_test (26 packages loaded, 362 targets configured).
+INFO: Found 1 test target...
+INFO: From Testing //:hello_test:
+==================== Test output for //:hello_test:
+Running main() from gmock_main.cc
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from HelloTest
+[ RUN      ] HelloTest.BasicAssertions
+[       OK ] HelloTest.BasicAssertions (0 ms)
+[----------] 1 test from HelloTest (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[  PASSED  ] 1 test.
+================================================================================
+Target //:hello_test up-to-date:
+  bazel-bin/hello_test
+INFO: Elapsed time: 4.190s, Critical Path: 3.05s
+INFO: 27 processes: 8 internal, 19 linux-sandbox.
+INFO: Build completed successfully, 27 total actions
+//:hello_test                                                     PASSED in 0.1s
+
+INFO: Build completed successfully, 27 total actions
+
+ +Congratulations! You've successfully built and run a test binary using +GoogleTest. + +## Next steps + +* [Check out the Primer](primer.md) to start learning how to write simple + tests. +* [See the code samples](samples.md) for more examples showing how to use a + variety of GoogleTest features. diff --git a/Engine/lib/assimp/contrib/googletest/docs/quickstart-cmake.md b/Engine/lib/assimp/contrib/googletest/docs/quickstart-cmake.md new file mode 100644 index 000000000..4e422b74f --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/quickstart-cmake.md @@ -0,0 +1,157 @@ +# Quickstart: Building with CMake + +This tutorial aims to get you up and running with GoogleTest using CMake. If +you're using GoogleTest for the first time or need a refresher, we recommend +this tutorial as a starting point. If your project uses Bazel, see the +[Quickstart for Bazel](quickstart-bazel.md) instead. + +## Prerequisites + +To complete this tutorial, you'll need: + +* A compatible operating system (e.g. Linux, macOS, Windows). +* A compatible C++ compiler that supports at least C++14. +* [CMake](https://cmake.org/) and a compatible build tool for building the + project. + * Compatible build tools include + [Make](https://www.gnu.org/software/make/), + [Ninja](https://ninja-build.org/), and others - see + [CMake Generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html) + for more information. + +See [Supported Platforms](platforms.md) for more information about platforms +compatible with GoogleTest. + +If you don't already have CMake installed, see the +[CMake installation guide](https://cmake.org/install). + +{: .callout .note} +Note: The terminal commands in this tutorial show a Unix shell prompt, but the +commands work on the Windows command line as well. + +## Set up a project + +CMake uses a file named `CMakeLists.txt` to configure the build system for a +project. You'll use this file to set up your project and declare a dependency on +GoogleTest. + +First, create a directory for your project: + +``` +$ mkdir my_project && cd my_project +``` + +Next, you'll create the `CMakeLists.txt` file and declare a dependency on +GoogleTest. There are many ways to express dependencies in the CMake ecosystem; +in this quickstart, you'll use the +[`FetchContent` CMake module](https://cmake.org/cmake/help/latest/module/FetchContent.html). +To do this, in your project directory (`my_project`), create a file named +`CMakeLists.txt` with the following contents: + +```cmake +cmake_minimum_required(VERSION 3.14) +project(my_project) + +# GoogleTest requires at least C++14 +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +``` + +The above configuration declares a dependency on GoogleTest which is downloaded +from GitHub. In the above example, `03597a01ee50ed33e9dfd640b249b4be3799d395` is +the Git commit hash of the GoogleTest version to use; we recommend updating the +hash often to point to the latest version. + +For more information about how to create `CMakeLists.txt` files, see the +[CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html). + +## Create and run a binary + +With GoogleTest declared as a dependency, you can use GoogleTest code within +your own project. + +As an example, create a file named `hello_test.cc` in your `my_project` +directory with the following contents: + +```cpp +#include + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} +``` + +GoogleTest provides [assertions](primer.md#assertions) that you use to test the +behavior of your code. The above sample includes the main GoogleTest header file +and demonstrates some basic assertions. + +To build the code, add the following to the end of your `CMakeLists.txt` file: + +```cmake +enable_testing() + +add_executable( + hello_test + hello_test.cc +) +target_link_libraries( + hello_test + GTest::gtest_main +) + +include(GoogleTest) +gtest_discover_tests(hello_test) +``` + +The above configuration enables testing in CMake, declares the C++ test binary +you want to build (`hello_test`), and links it to GoogleTest (`gtest_main`). The +last two lines enable CMake's test runner to discover the tests included in the +binary, using the +[`GoogleTest` CMake module](https://cmake.org/cmake/help/git-stage/module/GoogleTest.html). + +Now you can build and run your test: + +
+my_project$ cmake -S . -B build
+-- The C compiler identification is GNU 10.2.1
+-- The CXX compiler identification is GNU 10.2.1
+...
+-- Build files have been written to: .../my_project/build
+
+my_project$ cmake --build build
+Scanning dependencies of target gtest
+...
+[100%] Built target gmock_main
+
+my_project$ cd build && ctest
+Test project .../my_project/build
+    Start 1: HelloTest.BasicAssertions
+1/1 Test #1: HelloTest.BasicAssertions ........   Passed    0.00 sec
+
+100% tests passed, 0 tests failed out of 1
+
+Total Test time (real) =   0.01 sec
+
+ +Congratulations! You've successfully built and run a test binary using +GoogleTest. + +## Next steps + +* [Check out the Primer](primer.md) to start learning how to write simple + tests. +* [See the code samples](samples.md) for more examples showing how to use a + variety of GoogleTest features. diff --git a/Engine/lib/assimp/contrib/googletest/docs/reference/actions.md b/Engine/lib/assimp/contrib/googletest/docs/reference/actions.md new file mode 100644 index 000000000..ab81a129e --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/reference/actions.md @@ -0,0 +1,115 @@ +# Actions Reference + +[**Actions**](../gmock_for_dummies.md#actions-what-should-it-do) specify what a +mock function should do when invoked. This page lists the built-in actions +provided by GoogleTest. All actions are defined in the `::testing` namespace. + +## Returning a Value + +| Action | Description | +| :-------------------------------- | :-------------------------------------------- | +| `Return()` | Return from a `void` mock function. | +| `Return(value)` | Return `value`. If the type of `value` is different to the mock function's return type, `value` is converted to the latter type at the time the expectation is set, not when the action is executed. | +| `ReturnArg()` | Return the `N`-th (0-based) argument. | +| `ReturnNew(a1, ..., ak)` | Return `new T(a1, ..., ak)`; a different object is created each time. | +| `ReturnNull()` | Return a null pointer. | +| `ReturnPointee(ptr)` | Return the value pointed to by `ptr`. | +| `ReturnRef(variable)` | Return a reference to `variable`. | +| `ReturnRefOfCopy(value)` | Return a reference to a copy of `value`; the copy lives as long as the action. | +| `ReturnRoundRobin({a1, ..., ak})` | Each call will return the next `ai` in the list, starting at the beginning when the end of the list is reached. | + +## Side Effects + +| Action | Description | +| :--------------------------------- | :-------------------------------------- | +| `Assign(&variable, value)` | Assign `value` to variable. | +| `DeleteArg()` | Delete the `N`-th (0-based) argument, which must be a pointer. | +| `SaveArg(pointer)` | Save the `N`-th (0-based) argument to `*pointer`. | +| `SaveArgPointee(pointer)` | Save the value pointed to by the `N`-th (0-based) argument to `*pointer`. | +| `SetArgReferee(value)` | Assign `value` to the variable referenced by the `N`-th (0-based) argument. | +| `SetArgPointee(value)` | Assign `value` to the variable pointed by the `N`-th (0-based) argument. | +| `SetArgumentPointee(value)` | Same as `SetArgPointee(value)`. Deprecated. Will be removed in v1.7.0. | +| `SetArrayArgument(first, last)` | Copies the elements in source range [`first`, `last`) to the array pointed to by the `N`-th (0-based) argument, which can be either a pointer or an iterator. The action does not take ownership of the elements in the source range. | +| `SetErrnoAndReturn(error, value)` | Set `errno` to `error` and return `value`. | +| `Throw(exception)` | Throws the given exception, which can be any copyable value. Available since v1.1.0. | + +## Using a Function, Functor, or Lambda as an Action + +In the following, by "callable" we mean a free function, `std::function`, +functor, or lambda. + +| Action | Description | +| :---------------------------------- | :------------------------------------- | +| `f` | Invoke `f` with the arguments passed to the mock function, where `f` is a callable. | +| `Invoke(f)` | Invoke `f` with the arguments passed to the mock function, where `f` can be a global/static function or a functor. | +| `Invoke(object_pointer, &class::method)` | Invoke the method on the object with the arguments passed to the mock function. | +| `InvokeWithoutArgs(f)` | Invoke `f`, which can be a global/static function or a functor. `f` must take no arguments. | +| `InvokeWithoutArgs(object_pointer, &class::method)` | Invoke the method on the object, which takes no arguments. | +| `InvokeArgument(arg1, arg2, ..., argk)` | Invoke the mock function's `N`-th (0-based) argument, which must be a function or a functor, with the `k` arguments. | + +The return value of the invoked function is used as the return value of the +action. + +When defining a callable to be used with `Invoke*()`, you can declare any unused +parameters as `Unused`: + +```cpp +using ::testing::Invoke; +double Distance(Unused, double x, double y) { return sqrt(x*x + y*y); } +... +EXPECT_CALL(mock, Foo("Hi", _, _)).WillOnce(Invoke(Distance)); +``` + +`Invoke(callback)` and `InvokeWithoutArgs(callback)` take ownership of +`callback`, which must be permanent. The type of `callback` must be a base +callback type instead of a derived one, e.g. + +```cpp + BlockingClosure* done = new BlockingClosure; + ... Invoke(done) ...; // This won't compile! + + Closure* done2 = new BlockingClosure; + ... Invoke(done2) ...; // This works. +``` + +In `InvokeArgument(...)`, if an argument needs to be passed by reference, +wrap it inside `std::ref()`. For example, + +```cpp +using ::testing::InvokeArgument; +... +InvokeArgument<2>(5, string("Hi"), std::ref(foo)) +``` + +calls the mock function's #2 argument, passing to it `5` and `string("Hi")` by +value, and `foo` by reference. + +## Default Action + +| Action | Description | +| :------------ | :----------------------------------------------------- | +| `DoDefault()` | Do the default action (specified by `ON_CALL()` or the built-in one). | + +{: .callout .note} +**Note:** due to technical reasons, `DoDefault()` cannot be used inside a +composite action - trying to do so will result in a run-time error. + +## Composite Actions + +| Action | Description | +| :----------------------------- | :------------------------------------------ | +| `DoAll(a1, a2, ..., an)` | Do all actions `a1` to `an` and return the result of `an` in each invocation. The first `n - 1` sub-actions must return void and will receive a readonly view of the arguments. | +| `IgnoreResult(a)` | Perform action `a` and ignore its result. `a` must not return void. | +| `WithArg(a)` | Pass the `N`-th (0-based) argument of the mock function to action `a` and perform it. | +| `WithArgs(a)` | Pass the selected (0-based) arguments of the mock function to action `a` and perform it. | +| `WithoutArgs(a)` | Perform action `a` without any arguments. | + +## Defining Actions + +| Macro | Description | +| :--------------------------------- | :-------------------------------------- | +| `ACTION(Sum) { return arg0 + arg1; }` | Defines an action `Sum()` to return the sum of the mock function's argument #0 and #1. | +| `ACTION_P(Plus, n) { return arg0 + n; }` | Defines an action `Plus(n)` to return the sum of the mock function's argument #0 and `n`. | +| `ACTION_Pk(Foo, p1, ..., pk) { statements; }` | Defines a parameterized action `Foo(p1, ..., pk)` to execute the given `statements`. | + +The `ACTION*` macros cannot be used inside a function or class. diff --git a/Engine/lib/assimp/contrib/googletest/docs/reference/assertions.md b/Engine/lib/assimp/contrib/googletest/docs/reference/assertions.md new file mode 100644 index 000000000..aa1dbc04b --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/reference/assertions.md @@ -0,0 +1,633 @@ +# Assertions Reference + +This page lists the assertion macros provided by GoogleTest for verifying code +behavior. To use them, include the header `gtest/gtest.h`. + +The majority of the macros listed below come as a pair with an `EXPECT_` variant +and an `ASSERT_` variant. Upon failure, `EXPECT_` macros generate nonfatal +failures and allow the current function to continue running, while `ASSERT_` +macros generate fatal failures and abort the current function. + +All assertion macros support streaming a custom failure message into them with +the `<<` operator, for example: + +```cpp +EXPECT_TRUE(my_condition) << "My condition is not true"; +``` + +Anything that can be streamed to an `ostream` can be streamed to an assertion +macro—in particular, C strings and string objects. If a wide string (`wchar_t*`, +`TCHAR*` in `UNICODE` mode on Windows, or `std::wstring`) is streamed to an +assertion, it will be translated to UTF-8 when printed. + +## Explicit Success and Failure {#success-failure} + +The assertions in this section generate a success or failure directly instead of +testing a value or expression. These are useful when control flow, rather than a +Boolean expression, determines the test's success or failure, as shown by the +following example: + +```c++ +switch(expression) { + case 1: + ... some checks ... + case 2: + ... some other checks ... + default: + FAIL() << "We shouldn't get here."; +} +``` + +### SUCCEED {#SUCCEED} + +`SUCCEED()` + +Generates a success. This *does not* make the overall test succeed. A test is +considered successful only if none of its assertions fail during its execution. + +The `SUCCEED` assertion is purely documentary and currently doesn't generate any +user-visible output. However, we may add `SUCCEED` messages to GoogleTest output +in the future. + +### FAIL {#FAIL} + +`FAIL()` + +Generates a fatal failure, which returns from the current function. + +Can only be used in functions that return `void`. See +[Assertion Placement](../advanced.md#assertion-placement) for more information. + +### ADD_FAILURE {#ADD_FAILURE} + +`ADD_FAILURE()` + +Generates a nonfatal failure, which allows the current function to continue +running. + +### ADD_FAILURE_AT {#ADD_FAILURE_AT} + +`ADD_FAILURE_AT(`*`file_path`*`,`*`line_number`*`)` + +Generates a nonfatal failure at the file and line number specified. + +## Generalized Assertion {#generalized} + +The following assertion allows [matchers](matchers.md) to be used to verify +values. + +### EXPECT_THAT {#EXPECT_THAT} + +`EXPECT_THAT(`*`value`*`,`*`matcher`*`)` \ +`ASSERT_THAT(`*`value`*`,`*`matcher`*`)` + +Verifies that *`value`* matches the [matcher](matchers.md) *`matcher`*. + +For example, the following code verifies that the string `value1` starts with +`"Hello"`, `value2` matches a regular expression, and `value3` is between 5 and +10: + +```cpp +#include + +using ::testing::AllOf; +using ::testing::Gt; +using ::testing::Lt; +using ::testing::MatchesRegex; +using ::testing::StartsWith; + +... +EXPECT_THAT(value1, StartsWith("Hello")); +EXPECT_THAT(value2, MatchesRegex("Line \\d+")); +ASSERT_THAT(value3, AllOf(Gt(5), Lt(10))); +``` + +Matchers enable assertions of this form to read like English and generate +informative failure messages. For example, if the above assertion on `value1` +fails, the resulting message will be similar to the following: + +``` +Value of: value1 + Actual: "Hi, world!" +Expected: starts with "Hello" +``` + +GoogleTest provides a built-in library of matchers—see the +[Matchers Reference](matchers.md). It is also possible to write your own +matchers—see [Writing New Matchers Quickly](../gmock_cook_book.md#NewMatchers). +The use of matchers makes `EXPECT_THAT` a powerful, extensible assertion. + +*The idea for this assertion was borrowed from Joe Walnes' Hamcrest project, +which adds `assertThat()` to JUnit.* + +## Boolean Conditions {#boolean} + +The following assertions test Boolean conditions. + +### EXPECT_TRUE {#EXPECT_TRUE} + +`EXPECT_TRUE(`*`condition`*`)` \ +`ASSERT_TRUE(`*`condition`*`)` + +Verifies that *`condition`* is true. + +### EXPECT_FALSE {#EXPECT_FALSE} + +`EXPECT_FALSE(`*`condition`*`)` \ +`ASSERT_FALSE(`*`condition`*`)` + +Verifies that *`condition`* is false. + +## Binary Comparison {#binary-comparison} + +The following assertions compare two values. The value arguments must be +comparable by the assertion's comparison operator, otherwise a compiler error +will result. + +If an argument supports the `<<` operator, it will be called to print the +argument when the assertion fails. Otherwise, GoogleTest will attempt to print +them in the best way it can—see +[Teaching GoogleTest How to Print Your Values](../advanced.md#teaching-googletest-how-to-print-your-values). + +Arguments are always evaluated exactly once, so it's OK for the arguments to +have side effects. However, the argument evaluation order is undefined and +programs should not depend on any particular argument evaluation order. + +These assertions work with both narrow and wide string objects (`string` and +`wstring`). + +See also the [Floating-Point Comparison](#floating-point) assertions to compare +floating-point numbers and avoid problems caused by rounding. + +### EXPECT_EQ {#EXPECT_EQ} + +`EXPECT_EQ(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_EQ(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`==`*`val2`*. + +Does pointer equality on pointers. If used on two C strings, it tests if they +are in the same memory location, not if they have the same value. Use +[`EXPECT_STREQ`](#EXPECT_STREQ) to compare C strings (e.g. `const char*`) by +value. + +When comparing a pointer to `NULL`, use `EXPECT_EQ(`*`ptr`*`, nullptr)` instead +of `EXPECT_EQ(`*`ptr`*`, NULL)`. + +### EXPECT_NE {#EXPECT_NE} + +`EXPECT_NE(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_NE(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`!=`*`val2`*. + +Does pointer equality on pointers. If used on two C strings, it tests if they +are in different memory locations, not if they have different values. Use +[`EXPECT_STRNE`](#EXPECT_STRNE) to compare C strings (e.g. `const char*`) by +value. + +When comparing a pointer to `NULL`, use `EXPECT_NE(`*`ptr`*`, nullptr)` instead +of `EXPECT_NE(`*`ptr`*`, NULL)`. + +### EXPECT_LT {#EXPECT_LT} + +`EXPECT_LT(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_LT(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`<`*`val2`*. + +### EXPECT_LE {#EXPECT_LE} + +`EXPECT_LE(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_LE(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`<=`*`val2`*. + +### EXPECT_GT {#EXPECT_GT} + +`EXPECT_GT(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_GT(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`>`*`val2`*. + +### EXPECT_GE {#EXPECT_GE} + +`EXPECT_GE(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_GE(`*`val1`*`,`*`val2`*`)` + +Verifies that *`val1`*`>=`*`val2`*. + +## String Comparison {#c-strings} + +The following assertions compare two **C strings**. To compare two `string` +objects, use [`EXPECT_EQ`](#EXPECT_EQ) or [`EXPECT_NE`](#EXPECT_NE) instead. + +These assertions also accept wide C strings (`wchar_t*`). If a comparison of two +wide strings fails, their values will be printed as UTF-8 narrow strings. + +To compare a C string with `NULL`, use `EXPECT_EQ(`*`c_string`*`, nullptr)` or +`EXPECT_NE(`*`c_string`*`, nullptr)`. + +### EXPECT_STREQ {#EXPECT_STREQ} + +`EXPECT_STREQ(`*`str1`*`,`*`str2`*`)` \ +`ASSERT_STREQ(`*`str1`*`,`*`str2`*`)` + +Verifies that the two C strings *`str1`* and *`str2`* have the same contents. + +### EXPECT_STRNE {#EXPECT_STRNE} + +`EXPECT_STRNE(`*`str1`*`,`*`str2`*`)` \ +`ASSERT_STRNE(`*`str1`*`,`*`str2`*`)` + +Verifies that the two C strings *`str1`* and *`str2`* have different contents. + +### EXPECT_STRCASEEQ {#EXPECT_STRCASEEQ} + +`EXPECT_STRCASEEQ(`*`str1`*`,`*`str2`*`)` \ +`ASSERT_STRCASEEQ(`*`str1`*`,`*`str2`*`)` + +Verifies that the two C strings *`str1`* and *`str2`* have the same contents, +ignoring case. + +### EXPECT_STRCASENE {#EXPECT_STRCASENE} + +`EXPECT_STRCASENE(`*`str1`*`,`*`str2`*`)` \ +`ASSERT_STRCASENE(`*`str1`*`,`*`str2`*`)` + +Verifies that the two C strings *`str1`* and *`str2`* have different contents, +ignoring case. + +## Floating-Point Comparison {#floating-point} + +The following assertions compare two floating-point values. + +Due to rounding errors, it is very unlikely that two floating-point values will +match exactly, so `EXPECT_EQ` is not suitable. In general, for floating-point +comparison to make sense, the user needs to carefully choose the error bound. + +GoogleTest also provides assertions that use a default error bound based on +Units in the Last Place (ULPs). To learn more about ULPs, see the article +[Comparing Floating Point Numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). + +### EXPECT_FLOAT_EQ {#EXPECT_FLOAT_EQ} + +`EXPECT_FLOAT_EQ(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_FLOAT_EQ(`*`val1`*`,`*`val2`*`)` + +Verifies that the two `float` values *`val1`* and *`val2`* are approximately +equal, to within 4 ULPs from each other. + +### EXPECT_DOUBLE_EQ {#EXPECT_DOUBLE_EQ} + +`EXPECT_DOUBLE_EQ(`*`val1`*`,`*`val2`*`)` \ +`ASSERT_DOUBLE_EQ(`*`val1`*`,`*`val2`*`)` + +Verifies that the two `double` values *`val1`* and *`val2`* are approximately +equal, to within 4 ULPs from each other. + +### EXPECT_NEAR {#EXPECT_NEAR} + +`EXPECT_NEAR(`*`val1`*`,`*`val2`*`,`*`abs_error`*`)` \ +`ASSERT_NEAR(`*`val1`*`,`*`val2`*`,`*`abs_error`*`)` + +Verifies that the difference between *`val1`* and *`val2`* does not exceed the +absolute error bound *`abs_error`*. + +## Exception Assertions {#exceptions} + +The following assertions verify that a piece of code throws, or does not throw, +an exception. Usage requires exceptions to be enabled in the build environment. + +Note that the piece of code under test can be a compound statement, for example: + +```cpp +EXPECT_NO_THROW({ + int n = 5; + DoSomething(&n); +}); +``` + +### EXPECT_THROW {#EXPECT_THROW} + +`EXPECT_THROW(`*`statement`*`,`*`exception_type`*`)` \ +`ASSERT_THROW(`*`statement`*`,`*`exception_type`*`)` + +Verifies that *`statement`* throws an exception of type *`exception_type`*. + +### EXPECT_ANY_THROW {#EXPECT_ANY_THROW} + +`EXPECT_ANY_THROW(`*`statement`*`)` \ +`ASSERT_ANY_THROW(`*`statement`*`)` + +Verifies that *`statement`* throws an exception of any type. + +### EXPECT_NO_THROW {#EXPECT_NO_THROW} + +`EXPECT_NO_THROW(`*`statement`*`)` \ +`ASSERT_NO_THROW(`*`statement`*`)` + +Verifies that *`statement`* does not throw any exception. + +## Predicate Assertions {#predicates} + +The following assertions enable more complex predicates to be verified while +printing a more clear failure message than if `EXPECT_TRUE` were used alone. + +### EXPECT_PRED* {#EXPECT_PRED} + +`EXPECT_PRED1(`*`pred`*`,`*`val1`*`)` \ +`EXPECT_PRED2(`*`pred`*`,`*`val1`*`,`*`val2`*`)` \ +`EXPECT_PRED3(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \ +`EXPECT_PRED4(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` \ +`EXPECT_PRED5(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)` + +`ASSERT_PRED1(`*`pred`*`,`*`val1`*`)` \ +`ASSERT_PRED2(`*`pred`*`,`*`val1`*`,`*`val2`*`)` \ +`ASSERT_PRED3(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \ +`ASSERT_PRED4(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` \ +`ASSERT_PRED5(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)` + +Verifies that the predicate *`pred`* returns `true` when passed the given values +as arguments. + +The parameter *`pred`* is a function or functor that accepts as many arguments +as the corresponding macro accepts values. If *`pred`* returns `true` for the +given arguments, the assertion succeeds, otherwise the assertion fails. + +When the assertion fails, it prints the value of each argument. Arguments are +always evaluated exactly once. + +As an example, see the following code: + +```cpp +// Returns true if m and n have no common divisors except 1. +bool MutuallyPrime(int m, int n) { ... } +... +const int a = 3; +const int b = 4; +const int c = 10; +... +EXPECT_PRED2(MutuallyPrime, a, b); // Succeeds +EXPECT_PRED2(MutuallyPrime, b, c); // Fails +``` + +In the above example, the first assertion succeeds, and the second fails with +the following message: + +``` +MutuallyPrime(b, c) is false, where +b is 4 +c is 10 +``` + +Note that if the given predicate is an overloaded function or a function +template, the assertion macro might not be able to determine which version to +use, and it might be necessary to explicitly specify the type of the function. +For example, for a Boolean function `IsPositive()` overloaded to take either a +single `int` or `double` argument, it would be necessary to write one of the +following: + +```cpp +EXPECT_PRED1(static_cast(IsPositive), 5); +EXPECT_PRED1(static_cast(IsPositive), 3.14); +``` + +Writing simply `EXPECT_PRED1(IsPositive, 5);` would result in a compiler error. +Similarly, to use a template function, specify the template arguments: + +```cpp +template +bool IsNegative(T x) { + return x < 0; +} +... +EXPECT_PRED1(IsNegative, -5); // Must specify type for IsNegative +``` + +If a template has multiple parameters, wrap the predicate in parentheses so the +macro arguments are parsed correctly: + +```cpp +ASSERT_PRED2((MyPredicate), 5, 0); +``` + +### EXPECT_PRED_FORMAT* {#EXPECT_PRED_FORMAT} + +`EXPECT_PRED_FORMAT1(`*`pred_formatter`*`,`*`val1`*`)` \ +`EXPECT_PRED_FORMAT2(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`)` \ +`EXPECT_PRED_FORMAT3(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \ +`EXPECT_PRED_FORMAT4(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` +\ +`EXPECT_PRED_FORMAT5(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)` + +`ASSERT_PRED_FORMAT1(`*`pred_formatter`*`,`*`val1`*`)` \ +`ASSERT_PRED_FORMAT2(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`)` \ +`ASSERT_PRED_FORMAT3(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \ +`ASSERT_PRED_FORMAT4(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` +\ +`ASSERT_PRED_FORMAT5(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)` + +Verifies that the predicate *`pred_formatter`* succeeds when passed the given +values as arguments. + +The parameter *`pred_formatter`* is a *predicate-formatter*, which is a function +or functor with the signature: + +```cpp +testing::AssertionResult PredicateFormatter(const char* expr1, + const char* expr2, + ... + const char* exprn, + T1 val1, + T2 val2, + ... + Tn valn); +``` + +where *`val1`*, *`val2`*, ..., *`valn`* are the values of the predicate +arguments, and *`expr1`*, *`expr2`*, ..., *`exprn`* are the corresponding +expressions as they appear in the source code. The types `T1`, `T2`, ..., `Tn` +can be either value types or reference types; if an argument has type `T`, it +can be declared as either `T` or `const T&`, whichever is appropriate. For more +about the return type `testing::AssertionResult`, see +[Using a Function That Returns an AssertionResult](../advanced.md#using-a-function-that-returns-an-assertionresult). + +As an example, see the following code: + +```cpp +// Returns the smallest prime common divisor of m and n, +// or 1 when m and n are mutually prime. +int SmallestPrimeCommonDivisor(int m, int n) { ... } + +// Returns true if m and n have no common divisors except 1. +bool MutuallyPrime(int m, int n) { ... } + +// A predicate-formatter for asserting that two integers are mutually prime. +testing::AssertionResult AssertMutuallyPrime(const char* m_expr, + const char* n_expr, + int m, + int n) { + if (MutuallyPrime(m, n)) return testing::AssertionSuccess(); + + return testing::AssertionFailure() << m_expr << " and " << n_expr + << " (" << m << " and " << n << ") are not mutually prime, " + << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n); +} + +... +const int a = 3; +const int b = 4; +const int c = 10; +... +EXPECT_PRED_FORMAT2(AssertMutuallyPrime, a, b); // Succeeds +EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); // Fails +``` + +In the above example, the final assertion fails and the predicate-formatter +produces the following failure message: + +``` +b and c (4 and 10) are not mutually prime, as they have a common divisor 2 +``` + +## Windows HRESULT Assertions {#HRESULT} + +The following assertions test for `HRESULT` success or failure. For example: + +```cpp +CComPtr shell; +ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application")); +CComVariant empty; +ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty)); +``` + +The generated output contains the human-readable error message associated with +the returned `HRESULT` code. + +### EXPECT_HRESULT_SUCCEEDED {#EXPECT_HRESULT_SUCCEEDED} + +`EXPECT_HRESULT_SUCCEEDED(`*`expression`*`)` \ +`ASSERT_HRESULT_SUCCEEDED(`*`expression`*`)` + +Verifies that *`expression`* is a success `HRESULT`. + +### EXPECT_HRESULT_FAILED {#EXPECT_HRESULT_FAILED} + +`EXPECT_HRESULT_FAILED(`*`expression`*`)` \ +`ASSERT_HRESULT_FAILED(`*`expression`*`)` + +Verifies that *`expression`* is a failure `HRESULT`. + +## Death Assertions {#death} + +The following assertions verify that a piece of code causes the process to +terminate. For context, see [Death Tests](../advanced.md#death-tests). + +These assertions spawn a new process and execute the code under test in that +process. How that happens depends on the platform and the variable +`::testing::GTEST_FLAG(death_test_style)`, which is initialized from the +command-line flag `--gtest_death_test_style`. + +* On POSIX systems, `fork()` (or `clone()` on Linux) is used to spawn the + child, after which: + * If the variable's value is `"fast"`, the death test statement is + immediately executed. + * If the variable's value is `"threadsafe"`, the child process re-executes + the unit test binary just as it was originally invoked, but with some + extra flags to cause just the single death test under consideration to + be run. +* On Windows, the child is spawned using the `CreateProcess()` API, and + re-executes the binary to cause just the single death test under + consideration to be run - much like the `"threadsafe"` mode on POSIX. + +Other values for the variable are illegal and will cause the death test to fail. +Currently, the flag's default value is +**`"fast"`**. + +If the death test statement runs to completion without dying, the child process +will nonetheless terminate, and the assertion fails. + +Note that the piece of code under test can be a compound statement, for example: + +```cpp +EXPECT_DEATH({ + int n = 5; + DoSomething(&n); +}, "Error on line .* of DoSomething()"); +``` + +### EXPECT_DEATH {#EXPECT_DEATH} + +`EXPECT_DEATH(`*`statement`*`,`*`matcher`*`)` \ +`ASSERT_DEATH(`*`statement`*`,`*`matcher`*`)` + +Verifies that *`statement`* causes the process to terminate with a nonzero exit +status and produces `stderr` output that matches *`matcher`*. + +The parameter *`matcher`* is either a [matcher](matchers.md) for a `const +std::string&`, or a regular expression (see +[Regular Expression Syntax](../advanced.md#regular-expression-syntax))—a bare +string *`s`* (with no matcher) is treated as +[`ContainsRegex(s)`](matchers.md#string-matchers), **not** +[`Eq(s)`](matchers.md#generic-comparison). + +For example, the following code verifies that calling `DoSomething(42)` causes +the process to die with an error message that contains the text `My error`: + +```cpp +EXPECT_DEATH(DoSomething(42), "My error"); +``` + +### EXPECT_DEATH_IF_SUPPORTED {#EXPECT_DEATH_IF_SUPPORTED} + +`EXPECT_DEATH_IF_SUPPORTED(`*`statement`*`,`*`matcher`*`)` \ +`ASSERT_DEATH_IF_SUPPORTED(`*`statement`*`,`*`matcher`*`)` + +If death tests are supported, behaves the same as +[`EXPECT_DEATH`](#EXPECT_DEATH). Otherwise, verifies nothing. + +### EXPECT_DEBUG_DEATH {#EXPECT_DEBUG_DEATH} + +`EXPECT_DEBUG_DEATH(`*`statement`*`,`*`matcher`*`)` \ +`ASSERT_DEBUG_DEATH(`*`statement`*`,`*`matcher`*`)` + +In debug mode, behaves the same as [`EXPECT_DEATH`](#EXPECT_DEATH). When not in +debug mode (i.e. `NDEBUG` is defined), just executes *`statement`*. + +### EXPECT_EXIT {#EXPECT_EXIT} + +`EXPECT_EXIT(`*`statement`*`,`*`predicate`*`,`*`matcher`*`)` \ +`ASSERT_EXIT(`*`statement`*`,`*`predicate`*`,`*`matcher`*`)` + +Verifies that *`statement`* causes the process to terminate with an exit status +that satisfies *`predicate`*, and produces `stderr` output that matches +*`matcher`*. + +The parameter *`predicate`* is a function or functor that accepts an `int` exit +status and returns a `bool`. GoogleTest provides two predicates to handle common +cases: + +```cpp +// Returns true if the program exited normally with the given exit status code. +::testing::ExitedWithCode(exit_code); + +// Returns true if the program was killed by the given signal. +// Not available on Windows. +::testing::KilledBySignal(signal_number); +``` + +The parameter *`matcher`* is either a [matcher](matchers.md) for a `const +std::string&`, or a regular expression (see +[Regular Expression Syntax](../advanced.md#regular-expression-syntax))—a bare +string *`s`* (with no matcher) is treated as +[`ContainsRegex(s)`](matchers.md#string-matchers), **not** +[`Eq(s)`](matchers.md#generic-comparison). + +For example, the following code verifies that calling `NormalExit()` causes the +process to print a message containing the text `Success` to `stderr` and exit +with exit status code 0: + +```cpp +EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success"); +``` diff --git a/Engine/lib/assimp/contrib/googletest/docs/reference/matchers.md b/Engine/lib/assimp/contrib/googletest/docs/reference/matchers.md new file mode 100644 index 000000000..243e3f951 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/reference/matchers.md @@ -0,0 +1,302 @@ +# Matchers Reference + +A **matcher** matches a *single* argument. You can use it inside `ON_CALL()` or +`EXPECT_CALL()`, or use it to validate a value directly using two macros: + +| Macro | Description | +| :----------------------------------- | :------------------------------------ | +| `EXPECT_THAT(actual_value, matcher)` | Asserts that `actual_value` matches `matcher`. | +| `ASSERT_THAT(actual_value, matcher)` | The same as `EXPECT_THAT(actual_value, matcher)`, except that it generates a **fatal** failure. | + +{: .callout .warning} +**WARNING:** Equality matching via `EXPECT_THAT(actual_value, expected_value)` +is supported, however note that implicit conversions can cause surprising +results. For example, `EXPECT_THAT(some_bool, "some string")` will compile and +may pass unintentionally. + +**BEST PRACTICE:** Prefer to make the comparison explicit via +`EXPECT_THAT(actual_value, Eq(expected_value))` or `EXPECT_EQ(actual_value, +expected_value)`. + +Built-in matchers (where `argument` is the function argument, e.g. +`actual_value` in the example above, or when used in the context of +`EXPECT_CALL(mock_object, method(matchers))`, the arguments of `method`) are +divided into several categories. All matchers are defined in the `::testing` +namespace unless otherwise noted. + +## Wildcard + +Matcher | Description +:-------------------------- | :----------------------------------------------- +`_` | `argument` can be any value of the correct type. +`A()` or `An()` | `argument` can be any value of type `type`. + +## Generic Comparison + +| Matcher | Description | +| :--------------------- | :-------------------------------------------------- | +| `Eq(value)` or `value` | `argument == value` | +| `Ge(value)` | `argument >= value` | +| `Gt(value)` | `argument > value` | +| `Le(value)` | `argument <= value` | +| `Lt(value)` | `argument < value` | +| `Ne(value)` | `argument != value` | +| `IsFalse()` | `argument` evaluates to `false` in a Boolean context. | +| `IsTrue()` | `argument` evaluates to `true` in a Boolean context. | +| `IsNull()` | `argument` is a `NULL` pointer (raw or smart). | +| `NotNull()` | `argument` is a non-null pointer (raw or smart). | +| `Optional(m)` | `argument` is `optional<>` that contains a value matching `m`. (For testing whether an `optional<>` is set, check for equality with `nullopt`. You may need to use `Eq(nullopt)` if the inner type doesn't have `==`.)| +| `VariantWith(m)` | `argument` is `variant<>` that holds the alternative of type T with a value matching `m`. | +| `Ref(variable)` | `argument` is a reference to `variable`. | +| `TypedEq(value)` | `argument` has type `type` and is equal to `value`. You may need to use this instead of `Eq(value)` when the mock function is overloaded. | + +Except `Ref()`, these matchers make a *copy* of `value` in case it's modified or +destructed later. If the compiler complains that `value` doesn't have a public +copy constructor, try wrap it in `std::ref()`, e.g. +`Eq(std::ref(non_copyable_value))`. If you do that, make sure +`non_copyable_value` is not changed afterwards, or the meaning of your matcher +will be changed. + +`IsTrue` and `IsFalse` are useful when you need to use a matcher, or for types +that can be explicitly converted to Boolean, but are not implicitly converted to +Boolean. In other cases, you can use the basic +[`EXPECT_TRUE` and `EXPECT_FALSE`](assertions.md#boolean) assertions. + +## Floating-Point Matchers {#FpMatchers} + +| Matcher | Description | +| :------------------------------- | :--------------------------------- | +| `DoubleEq(a_double)` | `argument` is a `double` value approximately equal to `a_double`, treating two NaNs as unequal. | +| `FloatEq(a_float)` | `argument` is a `float` value approximately equal to `a_float`, treating two NaNs as unequal. | +| `NanSensitiveDoubleEq(a_double)` | `argument` is a `double` value approximately equal to `a_double`, treating two NaNs as equal. | +| `NanSensitiveFloatEq(a_float)` | `argument` is a `float` value approximately equal to `a_float`, treating two NaNs as equal. | +| `IsNan()` | `argument` is any floating-point type with a NaN value. | + +The above matchers use ULP-based comparison (the same as used in googletest). +They automatically pick a reasonable error bound based on the absolute value of +the expected value. `DoubleEq()` and `FloatEq()` conform to the IEEE standard, +which requires comparing two NaNs for equality to return false. The +`NanSensitive*` version instead treats two NaNs as equal, which is often what a +user wants. + +| Matcher | Description | +| :------------------------------------------------ | :----------------------- | +| `DoubleNear(a_double, max_abs_error)` | `argument` is a `double` value close to `a_double` (absolute error <= `max_abs_error`), treating two NaNs as unequal. | +| `FloatNear(a_float, max_abs_error)` | `argument` is a `float` value close to `a_float` (absolute error <= `max_abs_error`), treating two NaNs as unequal. | +| `NanSensitiveDoubleNear(a_double, max_abs_error)` | `argument` is a `double` value close to `a_double` (absolute error <= `max_abs_error`), treating two NaNs as equal. | +| `NanSensitiveFloatNear(a_float, max_abs_error)` | `argument` is a `float` value close to `a_float` (absolute error <= `max_abs_error`), treating two NaNs as equal. | + +## String Matchers + +The `argument` can be either a C string or a C++ string object: + +| Matcher | Description | +| :---------------------- | :------------------------------------------------- | +| `ContainsRegex(string)` | `argument` matches the given regular expression. | +| `EndsWith(suffix)` | `argument` ends with string `suffix`. | +| `HasSubstr(string)` | `argument` contains `string` as a sub-string. | +| `IsEmpty()` | `argument` is an empty string. | +| `MatchesRegex(string)` | `argument` matches the given regular expression with the match starting at the first character and ending at the last character. | +| `StartsWith(prefix)` | `argument` starts with string `prefix`. | +| `StrCaseEq(string)` | `argument` is equal to `string`, ignoring case. | +| `StrCaseNe(string)` | `argument` is not equal to `string`, ignoring case. | +| `StrEq(string)` | `argument` is equal to `string`. | +| `StrNe(string)` | `argument` is not equal to `string`. | +| `WhenBase64Unescaped(m)` | `argument` is a base-64 escaped string whose unescaped string matches `m`. The web-safe format from [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648#section-5) is supported. | + +`ContainsRegex()` and `MatchesRegex()` take ownership of the `RE` object. They +use the regular expression syntax defined +[here](../advanced.md#regular-expression-syntax). All of these matchers, except +`ContainsRegex()` and `MatchesRegex()` work for wide strings as well. + +## Container Matchers + +Most STL-style containers support `==`, so you can use `Eq(expected_container)` +or simply `expected_container` to match a container exactly. If you want to +write the elements in-line, match them more flexibly, or get more informative +messages, you can use: + +| Matcher | Description | +| :---------------------------------------- | :------------------------------- | +| `BeginEndDistanceIs(m)` | `argument` is a container whose `begin()` and `end()` iterators are separated by a number of increments matching `m`. E.g. `BeginEndDistanceIs(2)` or `BeginEndDistanceIs(Lt(2))`. For containers that define a `size()` method, `SizeIs(m)` may be more efficient. | +| `ContainerEq(container)` | The same as `Eq(container)` except that the failure message also includes which elements are in one container but not the other. | +| `Contains(e)` | `argument` contains an element that matches `e`, which can be either a value or a matcher. | +| `Contains(e).Times(n)` | `argument` contains elements that match `e`, which can be either a value or a matcher, and the number of matches is `n`, which can be either a value or a matcher. Unlike the plain `Contains` and `Each` this allows to check for arbitrary occurrences including testing for absence with `Contains(e).Times(0)`. | +| `Each(e)` | `argument` is a container where *every* element matches `e`, which can be either a value or a matcher. | +| `ElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, where the *i*-th element matches `ei`, which can be a value or a matcher. | +| `ElementsAreArray({e0, e1, ..., en})`, `ElementsAreArray(a_container)`, `ElementsAreArray(begin, end)`, `ElementsAreArray(array)`, or `ElementsAreArray(array, count)` | The same as `ElementsAre()` except that the expected element values/matchers come from an initializer list, STL-style container, iterator range, or C-style array. | +| `IsEmpty()` | `argument` is an empty container (`container.empty()`). | +| `IsSubsetOf({e0, e1, ..., en})`, `IsSubsetOf(a_container)`, `IsSubsetOf(begin, end)`, `IsSubsetOf(array)`, or `IsSubsetOf(array, count)` | `argument` matches `UnorderedElementsAre(x0, x1, ..., xk)` for some subset `{x0, x1, ..., xk}` of the expected matchers. | +| `IsSupersetOf({e0, e1, ..., en})`, `IsSupersetOf(a_container)`, `IsSupersetOf(begin, end)`, `IsSupersetOf(array)`, or `IsSupersetOf(array, count)` | Some subset of `argument` matches `UnorderedElementsAre(`expected matchers`)`. | +| `Pointwise(m, container)`, `Pointwise(m, {e0, e1, ..., en})` | `argument` contains the same number of elements as in `container`, and for all i, (the i-th element in `argument`, the i-th element in `container`) match `m`, which is a matcher on 2-tuples. E.g. `Pointwise(Le(), upper_bounds)` verifies that each element in `argument` doesn't exceed the corresponding element in `upper_bounds`. See more detail below. | +| `SizeIs(m)` | `argument` is a container whose size matches `m`. E.g. `SizeIs(2)` or `SizeIs(Lt(2))`. | +| `UnorderedElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, and under *some* permutation of the elements, each element matches an `ei` (for a different `i`), which can be a value or a matcher. | +| `UnorderedElementsAreArray({e0, e1, ..., en})`, `UnorderedElementsAreArray(a_container)`, `UnorderedElementsAreArray(begin, end)`, `UnorderedElementsAreArray(array)`, or `UnorderedElementsAreArray(array, count)` | The same as `UnorderedElementsAre()` except that the expected element values/matchers come from an initializer list, STL-style container, iterator range, or C-style array. | +| `UnorderedPointwise(m, container)`, `UnorderedPointwise(m, {e0, e1, ..., en})` | Like `Pointwise(m, container)`, but ignores the order of elements. | +| `WhenSorted(m)` | When `argument` is sorted using the `<` operator, it matches container matcher `m`. E.g. `WhenSorted(ElementsAre(1, 2, 3))` verifies that `argument` contains elements 1, 2, and 3, ignoring order. | +| `WhenSortedBy(comparator, m)` | The same as `WhenSorted(m)`, except that the given comparator instead of `<` is used to sort `argument`. E.g. `WhenSortedBy(std::greater(), ElementsAre(3, 2, 1))`. | + +**Notes:** + +* These matchers can also match: + 1. a native array passed by reference (e.g. in `Foo(const int (&a)[5])`), + and + 2. an array passed as a pointer and a count (e.g. in `Bar(const T* buffer, + int len)` -- see [Multi-argument Matchers](#MultiArgMatchers)). +* The array being matched may be multi-dimensional (i.e. its elements can be + arrays). +* `m` in `Pointwise(m, ...)` and `UnorderedPointwise(m, ...)` should be a + matcher for `::std::tuple` where `T` and `U` are the element type of + the actual container and the expected container, respectively. For example, + to compare two `Foo` containers where `Foo` doesn't support `operator==`, + one might write: + + ```cpp + MATCHER(FooEq, "") { + return std::get<0>(arg).Equals(std::get<1>(arg)); + } + ... + EXPECT_THAT(actual_foos, Pointwise(FooEq(), expected_foos)); + ``` + +## Member Matchers + +| Matcher | Description | +| :------------------------------ | :----------------------------------------- | +| `Field(&class::field, m)` | `argument.field` (or `argument->field` when `argument` is a plain pointer) matches matcher `m`, where `argument` is an object of type _class_. | +| `Field(field_name, &class::field, m)` | The same as the two-parameter version, but provides a better error message. | +| `Key(e)` | `argument.first` matches `e`, which can be either a value or a matcher. E.g. `Contains(Key(Le(5)))` can verify that a `map` contains a key `<= 5`. | +| `Pair(m1, m2)` | `argument` is an `std::pair` whose `first` field matches `m1` and `second` field matches `m2`. | +| `FieldsAre(m...)` | `argument` is a compatible object where each field matches piecewise with the matchers `m...`. A compatible object is any that supports the `std::tuple_size`+`get(obj)` protocol. In C++17 and up this also supports types compatible with structured bindings, like aggregates. | +| `Property(&class::property, m)` | `argument.property()` (or `argument->property()` when `argument` is a plain pointer) matches matcher `m`, where `argument` is an object of type _class_. The method `property()` must take no argument and be declared as `const`. | +| `Property(property_name, &class::property, m)` | The same as the two-parameter version, but provides a better error message. + +**Notes:** + +* You can use `FieldsAre()` to match any type that supports structured + bindings, such as `std::tuple`, `std::pair`, `std::array`, and aggregate + types. For example: + + ```cpp + std::tuple my_tuple{7, "hello world"}; + EXPECT_THAT(my_tuple, FieldsAre(Ge(0), HasSubstr("hello"))); + + struct MyStruct { + int value = 42; + std::string greeting = "aloha"; + }; + MyStruct s; + EXPECT_THAT(s, FieldsAre(42, "aloha")); + ``` + +* Don't use `Property()` against member functions that you do not own, because + taking addresses of functions is fragile and generally not part of the + contract of the function. + +## Matching the Result of a Function, Functor, or Callback + +| Matcher | Description | +| :--------------- | :------------------------------------------------ | +| `ResultOf(f, m)` | `f(argument)` matches matcher `m`, where `f` is a function or functor. | +| `ResultOf(result_description, f, m)` | The same as the two-parameter version, but provides a better error message. + +## Pointer Matchers + +| Matcher | Description | +| :------------------------ | :---------------------------------------------- | +| `Address(m)` | the result of `std::addressof(argument)` matches `m`. | +| `Pointee(m)` | `argument` (either a smart pointer or a raw pointer) points to a value that matches matcher `m`. | +| `Pointer(m)` | `argument` (either a smart pointer or a raw pointer) contains a pointer that matches `m`. `m` will match against the raw pointer regardless of the type of `argument`. | +| `WhenDynamicCastTo(m)` | when `argument` is passed through `dynamic_cast()`, it matches matcher `m`. | + +## Multi-argument Matchers {#MultiArgMatchers} + +Technically, all matchers match a *single* value. A "multi-argument" matcher is +just one that matches a *tuple*. The following matchers can be used to match a +tuple `(x, y)`: + +Matcher | Description +:------ | :---------- +`Eq()` | `x == y` +`Ge()` | `x >= y` +`Gt()` | `x > y` +`Le()` | `x <= y` +`Lt()` | `x < y` +`Ne()` | `x != y` + +You can use the following selectors to pick a subset of the arguments (or +reorder them) to participate in the matching: + +| Matcher | Description | +| :------------------------- | :---------------------------------------------- | +| `AllArgs(m)` | Equivalent to `m`. Useful as syntactic sugar in `.With(AllArgs(m))`. | +| `Args(m)` | The tuple of the `k` selected (using 0-based indices) arguments matches `m`, e.g. `Args<1, 2>(Eq())`. | + +## Composite Matchers + +You can make a matcher from one or more other matchers: + +| Matcher | Description | +| :------------------------------- | :-------------------------------------- | +| `AllOf(m1, m2, ..., mn)` | `argument` matches all of the matchers `m1` to `mn`. | +| `AllOfArray({m0, m1, ..., mn})`, `AllOfArray(a_container)`, `AllOfArray(begin, end)`, `AllOfArray(array)`, or `AllOfArray(array, count)` | The same as `AllOf()` except that the matchers come from an initializer list, STL-style container, iterator range, or C-style array. | +| `AnyOf(m1, m2, ..., mn)` | `argument` matches at least one of the matchers `m1` to `mn`. | +| `AnyOfArray({m0, m1, ..., mn})`, `AnyOfArray(a_container)`, `AnyOfArray(begin, end)`, `AnyOfArray(array)`, or `AnyOfArray(array, count)` | The same as `AnyOf()` except that the matchers come from an initializer list, STL-style container, iterator range, or C-style array. | +| `Not(m)` | `argument` doesn't match matcher `m`. | +| `Conditional(cond, m1, m2)` | Matches matcher `m1` if `cond` evaluates to true, else matches `m2`.| + +## Adapters for Matchers + +| Matcher | Description | +| :---------------------- | :------------------------------------ | +| `MatcherCast(m)` | casts matcher `m` to type `Matcher`. | +| `SafeMatcherCast(m)` | [safely casts](../gmock_cook_book.md#SafeMatcherCast) matcher `m` to type `Matcher`. | +| `Truly(predicate)` | `predicate(argument)` returns something considered by C++ to be true, where `predicate` is a function or functor. | + +`AddressSatisfies(callback)` and `Truly(callback)` take ownership of `callback`, +which must be a permanent callback. + +## Using Matchers as Predicates {#MatchersAsPredicatesCheat} + +| Matcher | Description | +| :---------------------------- | :------------------------------------------ | +| `Matches(m)(value)` | evaluates to `true` if `value` matches `m`. You can use `Matches(m)` alone as a unary functor. | +| `ExplainMatchResult(m, value, result_listener)` | evaluates to `true` if `value` matches `m`, explaining the result to `result_listener`. | +| `Value(value, m)` | evaluates to `true` if `value` matches `m`. | + +## Defining Matchers + +| Macro | Description | +| :----------------------------------- | :------------------------------------ | +| `MATCHER(IsEven, "") { return (arg % 2) == 0; }` | Defines a matcher `IsEven()` to match an even number. | +| `MATCHER_P(IsDivisibleBy, n, "") { *result_listener << "where the remainder is " << (arg % n); return (arg % n) == 0; }` | Defines a matcher `IsDivisibleBy(n)` to match a number divisible by `n`. | +| `MATCHER_P2(IsBetween, a, b, absl::StrCat(negation ? "isn't" : "is", " between ", PrintToString(a), " and ", PrintToString(b))) { return a <= arg && arg <= b; }` | Defines a matcher `IsBetween(a, b)` to match a value in the range [`a`, `b`]. | + +**Notes:** + +1. The `MATCHER*` macros cannot be used inside a function or class. +2. The matcher body must be *purely functional* (i.e. it cannot have any side + effect, and the result must not depend on anything other than the value + being matched and the matcher parameters). +3. You can use `PrintToString(x)` to convert a value `x` of any type to a + string. +4. You can use `ExplainMatchResult()` in a custom matcher to wrap another + matcher, for example: + + ```cpp + MATCHER_P(NestedPropertyMatches, matcher, "") { + return ExplainMatchResult(matcher, arg.nested().property(), result_listener); + } + ``` + +5. You can use `DescribeMatcher<>` to describe another matcher. For example: + + ```cpp + MATCHER_P(XAndYThat, matcher, + "X that " + DescribeMatcher(matcher, negation) + + (negation ? " or" : " and") + " Y that " + + DescribeMatcher(matcher, negation)) { + return ExplainMatchResult(matcher, arg.x(), result_listener) && + ExplainMatchResult(matcher, arg.y(), result_listener); + } + ``` diff --git a/Engine/lib/assimp/contrib/googletest/docs/reference/mocking.md b/Engine/lib/assimp/contrib/googletest/docs/reference/mocking.md new file mode 100644 index 000000000..e414ffbd0 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/reference/mocking.md @@ -0,0 +1,589 @@ +# Mocking Reference + +This page lists the facilities provided by GoogleTest for creating and working +with mock objects. To use them, include the header +`gmock/gmock.h`. + +## Macros {#macros} + +GoogleTest defines the following macros for working with mocks. + +### MOCK_METHOD {#MOCK_METHOD} + +`MOCK_METHOD(`*`return_type`*`,`*`method_name`*`, (`*`args...`*`));` \ +`MOCK_METHOD(`*`return_type`*`,`*`method_name`*`, (`*`args...`*`), +(`*`specs...`*`));` + +Defines a mock method *`method_name`* with arguments `(`*`args...`*`)` and +return type *`return_type`* within a mock class. + +The parameters of `MOCK_METHOD` mirror the method declaration. The optional +fourth parameter *`specs...`* is a comma-separated list of qualifiers. The +following qualifiers are accepted: + +| Qualifier | Meaning | +| -------------------------- | -------------------------------------------- | +| `const` | Makes the mocked method a `const` method. Required if overriding a `const` method. | +| `override` | Marks the method with `override`. Recommended if overriding a `virtual` method. | +| `noexcept` | Marks the method with `noexcept`. Required if overriding a `noexcept` method. | +| `Calltype(`*`calltype`*`)` | Sets the call type for the method, for example `Calltype(STDMETHODCALLTYPE)`. Useful on Windows. | +| `ref(`*`qualifier`*`)` | Marks the method with the given reference qualifier, for example `ref(&)` or `ref(&&)`. Required if overriding a method that has a reference qualifier. | + +Note that commas in arguments prevent `MOCK_METHOD` from parsing the arguments +correctly if they are not appropriately surrounded by parentheses. See the +following example: + +```cpp +class MyMock { + public: + // The following 2 lines will not compile due to commas in the arguments: + MOCK_METHOD(std::pair, GetPair, ()); // Error! + MOCK_METHOD(bool, CheckMap, (std::map, bool)); // Error! + + // One solution - wrap arguments that contain commas in parentheses: + MOCK_METHOD((std::pair), GetPair, ()); + MOCK_METHOD(bool, CheckMap, ((std::map), bool)); + + // Another solution - use type aliases: + using BoolAndInt = std::pair; + MOCK_METHOD(BoolAndInt, GetPair, ()); + using MapIntDouble = std::map; + MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool)); +}; +``` + +`MOCK_METHOD` must be used in the `public:` section of a mock class definition, +regardless of whether the method being mocked is `public`, `protected`, or +`private` in the base class. + +### EXPECT_CALL {#EXPECT_CALL} + +`EXPECT_CALL(`*`mock_object`*`,`*`method_name`*`(`*`matchers...`*`))` + +Creates an [expectation](../gmock_for_dummies.md#setting-expectations) that the +method *`method_name`* of the object *`mock_object`* is called with arguments +that match the given matchers *`matchers...`*. `EXPECT_CALL` must precede any +code that exercises the mock object. + +The parameter *`matchers...`* is a comma-separated list of +[matchers](../gmock_for_dummies.md#matchers-what-arguments-do-we-expect) that +correspond to each argument of the method *`method_name`*. The expectation will +apply only to calls of *`method_name`* whose arguments match all of the +matchers. If `(`*`matchers...`*`)` is omitted, the expectation behaves as if +each argument's matcher were a [wildcard matcher (`_`)](matchers.md#wildcard). +See the [Matchers Reference](matchers.md) for a list of all built-in matchers. + +The following chainable clauses can be used to modify the expectation, and they +must be used in the following order: + +```cpp +EXPECT_CALL(mock_object, method_name(matchers...)) + .With(multi_argument_matcher) // Can be used at most once + .Times(cardinality) // Can be used at most once + .InSequence(sequences...) // Can be used any number of times + .After(expectations...) // Can be used any number of times + .WillOnce(action) // Can be used any number of times + .WillRepeatedly(action) // Can be used at most once + .RetiresOnSaturation(); // Can be used at most once +``` + +See details for each modifier clause below. + +#### With {#EXPECT_CALL.With} + +`.With(`*`multi_argument_matcher`*`)` + +Restricts the expectation to apply only to mock function calls whose arguments +as a whole match the multi-argument matcher *`multi_argument_matcher`*. + +GoogleTest passes all of the arguments as one tuple into the matcher. The +parameter *`multi_argument_matcher`* must thus be a matcher of type +`Matcher>`, where `A1, ..., An` are the types of the +function arguments. + +For example, the following code sets the expectation that +`my_mock.SetPosition()` is called with any two arguments, the first argument +being less than the second: + +```cpp +using ::testing::_; +using ::testing::Lt; +... +EXPECT_CALL(my_mock, SetPosition(_, _)) + .With(Lt()); +``` + +GoogleTest provides some built-in matchers for 2-tuples, including the `Lt()` +matcher above. See [Multi-argument Matchers](matchers.md#MultiArgMatchers). + +The `With` clause can be used at most once on an expectation and must be the +first clause. + +#### Times {#EXPECT_CALL.Times} + +`.Times(`*`cardinality`*`)` + +Specifies how many times the mock function call is expected. + +The parameter *`cardinality`* represents the number of expected calls and can be +one of the following, all defined in the `::testing` namespace: + +| Cardinality | Meaning | +| ------------------- | --------------------------------------------------- | +| `AnyNumber()` | The function can be called any number of times. | +| `AtLeast(n)` | The function call is expected at least *n* times. | +| `AtMost(n)` | The function call is expected at most *n* times. | +| `Between(m, n)` | The function call is expected between *m* and *n* times, inclusive. | +| `Exactly(n)` or `n` | The function call is expected exactly *n* times. If *n* is 0, the call should never happen. | + +If the `Times` clause is omitted, GoogleTest infers the cardinality as follows: + +* If neither [`WillOnce`](#EXPECT_CALL.WillOnce) nor + [`WillRepeatedly`](#EXPECT_CALL.WillRepeatedly) are specified, the inferred + cardinality is `Times(1)`. +* If there are *n* `WillOnce` clauses and no `WillRepeatedly` clause, where + *n* >= 1, the inferred cardinality is `Times(n)`. +* If there are *n* `WillOnce` clauses and one `WillRepeatedly` clause, where + *n* >= 0, the inferred cardinality is `Times(AtLeast(n))`. + +The `Times` clause can be used at most once on an expectation. + +#### InSequence {#EXPECT_CALL.InSequence} + +`.InSequence(`*`sequences...`*`)` + +Specifies that the mock function call is expected in a certain sequence. + +The parameter *`sequences...`* is any number of [`Sequence`](#Sequence) objects. +Expected calls assigned to the same sequence are expected to occur in the order +the expectations are declared. + +For example, the following code sets the expectation that the `Reset()` method +of `my_mock` is called before both `GetSize()` and `Describe()`, and `GetSize()` +and `Describe()` can occur in any order relative to each other: + +```cpp +using ::testing::Sequence; +Sequence s1, s2; +... +EXPECT_CALL(my_mock, Reset()) + .InSequence(s1, s2); +EXPECT_CALL(my_mock, GetSize()) + .InSequence(s1); +EXPECT_CALL(my_mock, Describe()) + .InSequence(s2); +``` + +The `InSequence` clause can be used any number of times on an expectation. + +See also the [`InSequence` class](#InSequence). + +#### After {#EXPECT_CALL.After} + +`.After(`*`expectations...`*`)` + +Specifies that the mock function call is expected to occur after one or more +other calls. + +The parameter *`expectations...`* can be up to five +[`Expectation`](#Expectation) or [`ExpectationSet`](#ExpectationSet) objects. +The mock function call is expected to occur after all of the given expectations. + +For example, the following code sets the expectation that the `Describe()` +method of `my_mock` is called only after both `InitX()` and `InitY()` have been +called. + +```cpp +using ::testing::Expectation; +... +Expectation init_x = EXPECT_CALL(my_mock, InitX()); +Expectation init_y = EXPECT_CALL(my_mock, InitY()); +EXPECT_CALL(my_mock, Describe()) + .After(init_x, init_y); +``` + +The `ExpectationSet` object is helpful when the number of prerequisites for an +expectation is large or variable, for example: + +```cpp +using ::testing::ExpectationSet; +... +ExpectationSet all_inits; +// Collect all expectations of InitElement() calls +for (int i = 0; i < element_count; i++) { + all_inits += EXPECT_CALL(my_mock, InitElement(i)); +} +EXPECT_CALL(my_mock, Describe()) + .After(all_inits); // Expect Describe() call after all InitElement() calls +``` + +The `After` clause can be used any number of times on an expectation. + +#### WillOnce {#EXPECT_CALL.WillOnce} + +`.WillOnce(`*`action`*`)` + +Specifies the mock function's actual behavior when invoked, for a single +matching function call. + +The parameter *`action`* represents the +[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function +call will perform. See the [Actions Reference](actions.md) for a list of +built-in actions. + +The use of `WillOnce` implicitly sets a cardinality on the expectation when +`Times` is not specified. See [`Times`](#EXPECT_CALL.Times). + +Each matching function call will perform the next action in the order declared. +For example, the following code specifies that `my_mock.GetNumber()` is expected +to be called exactly 3 times and will return `1`, `2`, and `3` respectively on +the first, second, and third calls: + +```cpp +using ::testing::Return; +... +EXPECT_CALL(my_mock, GetNumber()) + .WillOnce(Return(1)) + .WillOnce(Return(2)) + .WillOnce(Return(3)); +``` + +The `WillOnce` clause can be used any number of times on an expectation. Unlike +`WillRepeatedly`, the action fed to each `WillOnce` call will be called at most +once, so may be a move-only type and/or have an `&&`-qualified call operator. + +#### WillRepeatedly {#EXPECT_CALL.WillRepeatedly} + +`.WillRepeatedly(`*`action`*`)` + +Specifies the mock function's actual behavior when invoked, for all subsequent +matching function calls. Takes effect after the actions specified in the +[`WillOnce`](#EXPECT_CALL.WillOnce) clauses, if any, have been performed. + +The parameter *`action`* represents the +[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function +call will perform. See the [Actions Reference](actions.md) for a list of +built-in actions. + +The use of `WillRepeatedly` implicitly sets a cardinality on the expectation +when `Times` is not specified. See [`Times`](#EXPECT_CALL.Times). + +If any `WillOnce` clauses have been specified, matching function calls will +perform those actions before the action specified by `WillRepeatedly`. See the +following example: + +```cpp +using ::testing::Return; +... +EXPECT_CALL(my_mock, GetName()) + .WillRepeatedly(Return("John Doe")); // Return "John Doe" on all calls + +EXPECT_CALL(my_mock, GetNumber()) + .WillOnce(Return(42)) // Return 42 on the first call + .WillRepeatedly(Return(7)); // Return 7 on all subsequent calls +``` + +The `WillRepeatedly` clause can be used at most once on an expectation. + +#### RetiresOnSaturation {#EXPECT_CALL.RetiresOnSaturation} + +`.RetiresOnSaturation()` + +Indicates that the expectation will no longer be active after the expected +number of matching function calls has been reached. + +The `RetiresOnSaturation` clause is only meaningful for expectations with an +upper-bounded cardinality. The expectation will *retire* (no longer match any +function calls) after it has been *saturated* (the upper bound has been +reached). See the following example: + +```cpp +using ::testing::_; +using ::testing::AnyNumber; +... +EXPECT_CALL(my_mock, SetNumber(_)) // Expectation 1 + .Times(AnyNumber()); +EXPECT_CALL(my_mock, SetNumber(7)) // Expectation 2 + .Times(2) + .RetiresOnSaturation(); +``` + +In the above example, the first two calls to `my_mock.SetNumber(7)` match +expectation 2, which then becomes inactive and no longer matches any calls. A +third call to `my_mock.SetNumber(7)` would then match expectation 1. Without +`RetiresOnSaturation()` on expectation 2, a third call to `my_mock.SetNumber(7)` +would match expectation 2 again, producing a failure since the limit of 2 calls +was exceeded. + +The `RetiresOnSaturation` clause can be used at most once on an expectation and +must be the last clause. + +### ON_CALL {#ON_CALL} + +`ON_CALL(`*`mock_object`*`,`*`method_name`*`(`*`matchers...`*`))` + +Defines what happens when the method *`method_name`* of the object +*`mock_object`* is called with arguments that match the given matchers +*`matchers...`*. Requires a modifier clause to specify the method's behavior. +*Does not* set any expectations that the method will be called. + +The parameter *`matchers...`* is a comma-separated list of +[matchers](../gmock_for_dummies.md#matchers-what-arguments-do-we-expect) that +correspond to each argument of the method *`method_name`*. The `ON_CALL` +specification will apply only to calls of *`method_name`* whose arguments match +all of the matchers. If `(`*`matchers...`*`)` is omitted, the behavior is as if +each argument's matcher were a [wildcard matcher (`_`)](matchers.md#wildcard). +See the [Matchers Reference](matchers.md) for a list of all built-in matchers. + +The following chainable clauses can be used to set the method's behavior, and +they must be used in the following order: + +```cpp +ON_CALL(mock_object, method_name(matchers...)) + .With(multi_argument_matcher) // Can be used at most once + .WillByDefault(action); // Required +``` + +See details for each modifier clause below. + +#### With {#ON_CALL.With} + +`.With(`*`multi_argument_matcher`*`)` + +Restricts the specification to only mock function calls whose arguments as a +whole match the multi-argument matcher *`multi_argument_matcher`*. + +GoogleTest passes all of the arguments as one tuple into the matcher. The +parameter *`multi_argument_matcher`* must thus be a matcher of type +`Matcher>`, where `A1, ..., An` are the types of the +function arguments. + +For example, the following code sets the default behavior when +`my_mock.SetPosition()` is called with any two arguments, the first argument +being less than the second: + +```cpp +using ::testing::_; +using ::testing::Lt; +using ::testing::Return; +... +ON_CALL(my_mock, SetPosition(_, _)) + .With(Lt()) + .WillByDefault(Return(true)); +``` + +GoogleTest provides some built-in matchers for 2-tuples, including the `Lt()` +matcher above. See [Multi-argument Matchers](matchers.md#MultiArgMatchers). + +The `With` clause can be used at most once with each `ON_CALL` statement. + +#### WillByDefault {#ON_CALL.WillByDefault} + +`.WillByDefault(`*`action`*`)` + +Specifies the default behavior of a matching mock function call. + +The parameter *`action`* represents the +[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function +call will perform. See the [Actions Reference](actions.md) for a list of +built-in actions. + +For example, the following code specifies that by default, a call to +`my_mock.Greet()` will return `"hello"`: + +```cpp +using ::testing::Return; +... +ON_CALL(my_mock, Greet()) + .WillByDefault(Return("hello")); +``` + +The action specified by `WillByDefault` is superseded by the actions specified +on a matching `EXPECT_CALL` statement, if any. See the +[`WillOnce`](#EXPECT_CALL.WillOnce) and +[`WillRepeatedly`](#EXPECT_CALL.WillRepeatedly) clauses of `EXPECT_CALL`. + +The `WillByDefault` clause must be used exactly once with each `ON_CALL` +statement. + +## Classes {#classes} + +GoogleTest defines the following classes for working with mocks. + +### DefaultValue {#DefaultValue} + +`::testing::DefaultValue` + +Allows a user to specify the default value for a type `T` that is both copyable +and publicly destructible (i.e. anything that can be used as a function return +type). For mock functions with a return type of `T`, this default value is +returned from function calls that do not specify an action. + +Provides the static methods `Set()`, `SetFactory()`, and `Clear()` to manage the +default value: + +```cpp +// Sets the default value to be returned. T must be copy constructible. +DefaultValue::Set(value); + +// Sets a factory. Will be invoked on demand. T must be move constructible. +T MakeT(); +DefaultValue::SetFactory(&MakeT); + +// Unsets the default value. +DefaultValue::Clear(); +``` + +### NiceMock {#NiceMock} + +`::testing::NiceMock` + +Represents a mock object that suppresses warnings on +[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The +template parameter `T` is any mock class, except for another `NiceMock`, +`NaggyMock`, or `StrictMock`. + +Usage of `NiceMock` is analogous to usage of `T`. `NiceMock` is a subclass +of `T`, so it can be used wherever an object of type `T` is accepted. In +addition, `NiceMock` can be constructed with any arguments that a constructor +of `T` accepts. + +For example, the following code suppresses warnings on the mock `my_mock` of +type `MockClass` if a method other than `DoSomething()` is called: + +```cpp +using ::testing::NiceMock; +... +NiceMock my_mock("some", "args"); +EXPECT_CALL(my_mock, DoSomething()); +... code that uses my_mock ... +``` + +`NiceMock` only works for mock methods defined using the `MOCK_METHOD` macro +directly in the definition of class `T`. If a mock method is defined in a base +class of `T`, a warning might still be generated. + +`NiceMock` might not work correctly if the destructor of `T` is not virtual. + +### NaggyMock {#NaggyMock} + +`::testing::NaggyMock` + +Represents a mock object that generates warnings on +[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The +template parameter `T` is any mock class, except for another `NiceMock`, +`NaggyMock`, or `StrictMock`. + +Usage of `NaggyMock` is analogous to usage of `T`. `NaggyMock` is a +subclass of `T`, so it can be used wherever an object of type `T` is accepted. +In addition, `NaggyMock` can be constructed with any arguments that a +constructor of `T` accepts. + +For example, the following code generates warnings on the mock `my_mock` of type +`MockClass` if a method other than `DoSomething()` is called: + +```cpp +using ::testing::NaggyMock; +... +NaggyMock my_mock("some", "args"); +EXPECT_CALL(my_mock, DoSomething()); +... code that uses my_mock ... +``` + +Mock objects of type `T` by default behave the same way as `NaggyMock`. + +### StrictMock {#StrictMock} + +`::testing::StrictMock` + +Represents a mock object that generates test failures on +[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The +template parameter `T` is any mock class, except for another `NiceMock`, +`NaggyMock`, or `StrictMock`. + +Usage of `StrictMock` is analogous to usage of `T`. `StrictMock` is a +subclass of `T`, so it can be used wherever an object of type `T` is accepted. +In addition, `StrictMock` can be constructed with any arguments that a +constructor of `T` accepts. + +For example, the following code generates a test failure on the mock `my_mock` +of type `MockClass` if a method other than `DoSomething()` is called: + +```cpp +using ::testing::StrictMock; +... +StrictMock my_mock("some", "args"); +EXPECT_CALL(my_mock, DoSomething()); +... code that uses my_mock ... +``` + +`StrictMock` only works for mock methods defined using the `MOCK_METHOD` +macro directly in the definition of class `T`. If a mock method is defined in a +base class of `T`, a failure might not be generated. + +`StrictMock` might not work correctly if the destructor of `T` is not +virtual. + +### Sequence {#Sequence} + +`::testing::Sequence` + +Represents a chronological sequence of expectations. See the +[`InSequence`](#EXPECT_CALL.InSequence) clause of `EXPECT_CALL` for usage. + +### InSequence {#InSequence} + +`::testing::InSequence` + +An object of this type causes all expectations encountered in its scope to be +put in an anonymous sequence. + +This allows more convenient expression of multiple expectations in a single +sequence: + +```cpp +using ::testing::InSequence; +{ + InSequence seq; + + // The following are expected to occur in the order declared. + EXPECT_CALL(...); + EXPECT_CALL(...); + ... + EXPECT_CALL(...); +} +``` + +The name of the `InSequence` object does not matter. + +### Expectation {#Expectation} + +`::testing::Expectation` + +Represents a mock function call expectation as created by +[`EXPECT_CALL`](#EXPECT_CALL): + +```cpp +using ::testing::Expectation; +Expectation my_expectation = EXPECT_CALL(...); +``` + +Useful for specifying sequences of expectations; see the +[`After`](#EXPECT_CALL.After) clause of `EXPECT_CALL`. + +### ExpectationSet {#ExpectationSet} + +`::testing::ExpectationSet` + +Represents a set of mock function call expectations. + +Use the `+=` operator to add [`Expectation`](#Expectation) objects to the set: + +```cpp +using ::testing::ExpectationSet; +ExpectationSet my_expectations; +my_expectations += EXPECT_CALL(...); +``` + +Useful for specifying sequences of expectations; see the +[`After`](#EXPECT_CALL.After) clause of `EXPECT_CALL`. diff --git a/Engine/lib/assimp/contrib/googletest/docs/reference/testing.md b/Engine/lib/assimp/contrib/googletest/docs/reference/testing.md new file mode 100644 index 000000000..17225a682 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/reference/testing.md @@ -0,0 +1,1432 @@ +# Testing Reference + + + +This page lists the facilities provided by GoogleTest for writing test programs. +To use them, include the header `gtest/gtest.h`. + +## Macros + +GoogleTest defines the following macros for writing tests. + +### TEST {#TEST} + +
+TEST(TestSuiteName, TestName) {
+  ... statements ...
+}
+
+ +Defines an individual test named *`TestName`* in the test suite +*`TestSuiteName`*, consisting of the given statements. + +Both arguments *`TestSuiteName`* and *`TestName`* must be valid C++ identifiers +and must not contain underscores (`_`). Tests in different test suites can have +the same individual name. + +The statements within the test body can be any code under test. +[Assertions](assertions.md) used within the test body determine the outcome of +the test. + +### TEST_F {#TEST_F} + +
+TEST_F(TestFixtureName, TestName) {
+  ... statements ...
+}
+
+ +Defines an individual test named *`TestName`* that uses the test fixture class +*`TestFixtureName`*. The test suite name is *`TestFixtureName`*. + +Both arguments *`TestFixtureName`* and *`TestName`* must be valid C++ +identifiers and must not contain underscores (`_`). *`TestFixtureName`* must be +the name of a test fixture class—see +[Test Fixtures](../primer.md#same-data-multiple-tests). + +The statements within the test body can be any code under test. +[Assertions](assertions.md) used within the test body determine the outcome of +the test. + +### TEST_P {#TEST_P} + +
+TEST_P(TestFixtureName, TestName) {
+  ... statements ...
+}
+
+ +Defines an individual value-parameterized test named *`TestName`* that uses the +test fixture class *`TestFixtureName`*. The test suite name is +*`TestFixtureName`*. + +Both arguments *`TestFixtureName`* and *`TestName`* must be valid C++ +identifiers and must not contain underscores (`_`). *`TestFixtureName`* must be +the name of a value-parameterized test fixture class—see +[Value-Parameterized Tests](../advanced.md#value-parameterized-tests). + +The statements within the test body can be any code under test. Within the test +body, the test parameter can be accessed with the `GetParam()` function (see +[`WithParamInterface`](#WithParamInterface)). For example: + +```cpp +TEST_P(MyTestSuite, DoesSomething) { + ... + EXPECT_TRUE(DoSomething(GetParam())); + ... +} +``` + +[Assertions](assertions.md) used within the test body determine the outcome of +the test. + +See also [`INSTANTIATE_TEST_SUITE_P`](#INSTANTIATE_TEST_SUITE_P). + +### INSTANTIATE_TEST_SUITE_P {#INSTANTIATE_TEST_SUITE_P} + +`INSTANTIATE_TEST_SUITE_P(`*`InstantiationName`*`,`*`TestSuiteName`*`,`*`param_generator`*`)` +\ +`INSTANTIATE_TEST_SUITE_P(`*`InstantiationName`*`,`*`TestSuiteName`*`,`*`param_generator`*`,`*`name_generator`*`)` + +Instantiates the value-parameterized test suite *`TestSuiteName`* (defined with +[`TEST_P`](#TEST_P)). + +The argument *`InstantiationName`* is a unique name for the instantiation of the +test suite, to distinguish between multiple instantiations. In test output, the +instantiation name is added as a prefix to the test suite name +*`TestSuiteName`*. + +The argument *`param_generator`* is one of the following GoogleTest-provided +functions that generate the test parameters, all defined in the `::testing` +namespace: + + + +| Parameter Generator | Behavior | +| ------------------- | ---------------------------------------------------- | +| `Range(begin, end [, step])` | Yields values `{begin, begin+step, begin+step+step, ...}`. The values do not include `end`. `step` defaults to 1. | +| `Values(v1, v2, ..., vN)` | Yields values `{v1, v2, ..., vN}`. | +| `ValuesIn(container)` or `ValuesIn(begin,end)` | Yields values from a C-style array, an STL-style container, or an iterator range `[begin, end)`. | +| `Bool()` | Yields sequence `{false, true}`. | +| `Combine(g1, g2, ..., gN)` | Yields as `std::tuple` *n*-tuples all combinations (Cartesian product) of the values generated by the given *n* generators `g1`, `g2`, ..., `gN`. | +| `ConvertGenerator(g)` | Yields values generated by generator `g`, `static_cast` to `T`. | + +The optional last argument *`name_generator`* is a function or functor that +generates custom test name suffixes based on the test parameters. The function +must accept an argument of type +[`TestParamInfo`](#TestParamInfo) and return a `std::string`. +The test name suffix can only contain alphanumeric characters and underscores. +GoogleTest provides [`PrintToStringParamName`](#PrintToStringParamName), or a +custom function can be used for more control: + +```cpp +INSTANTIATE_TEST_SUITE_P( + MyInstantiation, MyTestSuite, + testing::Values(...), + [](const testing::TestParamInfo& info) { + // Can use info.param here to generate the test suffix + std::string name = ... + return name; + }); +``` + +For more information, see +[Value-Parameterized Tests](../advanced.md#value-parameterized-tests). + +See also +[`GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST`](#GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST). + +### TYPED_TEST_SUITE {#TYPED_TEST_SUITE} + +`TYPED_TEST_SUITE(`*`TestFixtureName`*`,`*`Types`*`)` + +Defines a typed test suite based on the test fixture *`TestFixtureName`*. The +test suite name is *`TestFixtureName`*. + +The argument *`TestFixtureName`* is a fixture class template, parameterized by a +type, for example: + +```cpp +template +class MyFixture : public testing::Test { + public: + ... + using List = std::list; + static T shared_; + T value_; +}; +``` + +The argument *`Types`* is a [`Types`](#Types) object representing the list of +types to run the tests on, for example: + +```cpp +using MyTypes = ::testing::Types; +TYPED_TEST_SUITE(MyFixture, MyTypes); +``` + +The type alias (`using` or `typedef`) is necessary for the `TYPED_TEST_SUITE` +macro to parse correctly. + +See also [`TYPED_TEST`](#TYPED_TEST) and +[Typed Tests](../advanced.md#typed-tests) for more information. + +### TYPED_TEST {#TYPED_TEST} + +
+TYPED_TEST(TestSuiteName, TestName) {
+  ... statements ...
+}
+
+ +Defines an individual typed test named *`TestName`* in the typed test suite +*`TestSuiteName`*. The test suite must be defined with +[`TYPED_TEST_SUITE`](#TYPED_TEST_SUITE). + +Within the test body, the special name `TypeParam` refers to the type parameter, +and `TestFixture` refers to the fixture class. See the following example: + +```cpp +TYPED_TEST(MyFixture, Example) { + // Inside a test, refer to the special name TypeParam to get the type + // parameter. Since we are inside a derived class template, C++ requires + // us to visit the members of MyFixture via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the 'TestFixture::' + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the 'typename TestFixture::' + // prefix. The 'typename' is required to satisfy the compiler. + typename TestFixture::List values; + + values.push_back(n); + ... +} +``` + +For more information, see [Typed Tests](../advanced.md#typed-tests). + +### TYPED_TEST_SUITE_P {#TYPED_TEST_SUITE_P} + +`TYPED_TEST_SUITE_P(`*`TestFixtureName`*`)` + +Defines a type-parameterized test suite based on the test fixture +*`TestFixtureName`*. The test suite name is *`TestFixtureName`*. + +The argument *`TestFixtureName`* is a fixture class template, parameterized by a +type. See [`TYPED_TEST_SUITE`](#TYPED_TEST_SUITE) for an example. + +See also [`TYPED_TEST_P`](#TYPED_TEST_P) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests) for more +information. + +### TYPED_TEST_P {#TYPED_TEST_P} + +
+TYPED_TEST_P(TestSuiteName, TestName) {
+  ... statements ...
+}
+
+ +Defines an individual type-parameterized test named *`TestName`* in the +type-parameterized test suite *`TestSuiteName`*. The test suite must be defined +with [`TYPED_TEST_SUITE_P`](#TYPED_TEST_SUITE_P). + +Within the test body, the special name `TypeParam` refers to the type parameter, +and `TestFixture` refers to the fixture class. See [`TYPED_TEST`](#TYPED_TEST) +for an example. + +See also [`REGISTER_TYPED_TEST_SUITE_P`](#REGISTER_TYPED_TEST_SUITE_P) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests) for more +information. + +### REGISTER_TYPED_TEST_SUITE_P {#REGISTER_TYPED_TEST_SUITE_P} + +`REGISTER_TYPED_TEST_SUITE_P(`*`TestSuiteName`*`,`*`TestNames...`*`)` + +Registers the type-parameterized tests *`TestNames...`* of the test suite +*`TestSuiteName`*. The test suite and tests must be defined with +[`TYPED_TEST_SUITE_P`](#TYPED_TEST_SUITE_P) and [`TYPED_TEST_P`](#TYPED_TEST_P). + +For example: + +```cpp +// Define the test suite and tests. +TYPED_TEST_SUITE_P(MyFixture); +TYPED_TEST_P(MyFixture, HasPropertyA) { ... } +TYPED_TEST_P(MyFixture, HasPropertyB) { ... } + +// Register the tests in the test suite. +REGISTER_TYPED_TEST_SUITE_P(MyFixture, HasPropertyA, HasPropertyB); +``` + +See also [`INSTANTIATE_TYPED_TEST_SUITE_P`](#INSTANTIATE_TYPED_TEST_SUITE_P) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests) for more +information. + +### INSTANTIATE_TYPED_TEST_SUITE_P {#INSTANTIATE_TYPED_TEST_SUITE_P} + +`INSTANTIATE_TYPED_TEST_SUITE_P(`*`InstantiationName`*`,`*`TestSuiteName`*`,`*`Types`*`)` + +Instantiates the type-parameterized test suite *`TestSuiteName`*. The test suite +must be registered with +[`REGISTER_TYPED_TEST_SUITE_P`](#REGISTER_TYPED_TEST_SUITE_P). + +The argument *`InstantiationName`* is a unique name for the instantiation of the +test suite, to distinguish between multiple instantiations. In test output, the +instantiation name is added as a prefix to the test suite name +*`TestSuiteName`*. + +The argument *`Types`* is a [`Types`](#Types) object representing the list of +types to run the tests on, for example: + +```cpp +using MyTypes = ::testing::Types; +INSTANTIATE_TYPED_TEST_SUITE_P(MyInstantiation, MyFixture, MyTypes); +``` + +The type alias (`using` or `typedef`) is necessary for the +`INSTANTIATE_TYPED_TEST_SUITE_P` macro to parse correctly. + +For more information, see +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests). + +### FRIEND_TEST {#FRIEND_TEST} + +`FRIEND_TEST(`*`TestSuiteName`*`,`*`TestName`*`)` + +Within a class body, declares an individual test as a friend of the class, +enabling the test to access private class members. + +If the class is defined in a namespace, then in order to be friends of the +class, test fixtures and tests must be defined in the exact same namespace, +without inline or anonymous namespaces. + +For example, if the class definition looks like the following: + +```cpp +namespace my_namespace { + +class MyClass { + friend class MyClassTest; + FRIEND_TEST(MyClassTest, HasPropertyA); + FRIEND_TEST(MyClassTest, HasPropertyB); + ... definition of class MyClass ... +}; + +} // namespace my_namespace +``` + +Then the test code should look like: + +```cpp +namespace my_namespace { + +class MyClassTest : public testing::Test { + ... +}; + +TEST_F(MyClassTest, HasPropertyA) { ... } +TEST_F(MyClassTest, HasPropertyB) { ... } + +} // namespace my_namespace +``` + +See [Testing Private Code](../advanced.md#testing-private-code) for more +information. + +### SCOPED_TRACE {#SCOPED_TRACE} + +`SCOPED_TRACE(`*`message`*`)` + +Causes the current file name, line number, and the given message *`message`* to +be added to the failure message for each assertion failure that occurs in the +scope. + +For more information, see +[Adding Traces to Assertions](../advanced.md#adding-traces-to-assertions). + +See also the [`ScopedTrace` class](#ScopedTrace). + +### GTEST_SKIP {#GTEST_SKIP} + +`GTEST_SKIP()` + +Prevents further test execution at runtime. + +Can be used in individual test cases or in the `SetUp()` methods of test +environments or test fixtures (classes derived from the +[`Environment`](#Environment) or [`Test`](#Test) classes). If used in a global +test environment `SetUp()` method, it skips all tests in the test program. If +used in a test fixture `SetUp()` method, it skips all tests in the corresponding +test suite. + +Similar to assertions, `GTEST_SKIP` allows streaming a custom message into it. + +See [Skipping Test Execution](../advanced.md#skipping-test-execution) for more +information. + +### GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST {#GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST} + +`GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(`*`TestSuiteName`*`)` + +Allows the value-parameterized test suite *`TestSuiteName`* to be +uninstantiated. + +By default, every [`TEST_P`](#TEST_P) call without a corresponding +[`INSTANTIATE_TEST_SUITE_P`](#INSTANTIATE_TEST_SUITE_P) call causes a failing +test in the test suite `GoogleTestVerification`. +`GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST` suppresses this failure for the +given test suite. + +## Classes and types + +GoogleTest defines the following classes and types to help with writing tests. + +### AssertionResult {#AssertionResult} + +`testing::AssertionResult` + +A class for indicating whether an assertion was successful. + +When the assertion wasn't successful, the `AssertionResult` object stores a +non-empty failure message that can be retrieved with the object's `message()` +method. + +To create an instance of this class, use one of the factory functions +[`AssertionSuccess()`](#AssertionSuccess) or +[`AssertionFailure()`](#AssertionFailure). + +### AssertionException {#AssertionException} + +`testing::AssertionException` + +Exception which can be thrown from +[`TestEventListener::OnTestPartResult`](#TestEventListener::OnTestPartResult). + +### EmptyTestEventListener {#EmptyTestEventListener} + +`testing::EmptyTestEventListener` + +Provides an empty implementation of all methods in the +[`TestEventListener`](#TestEventListener) interface, such that a subclass only +needs to override the methods it cares about. + +### Environment {#Environment} + +`testing::Environment` + +Represents a global test environment. See +[Global Set-Up and Tear-Down](../advanced.md#global-set-up-and-tear-down). + +#### Protected Methods {#Environment-protected} + +##### SetUp {#Environment::SetUp} + +`virtual void Environment::SetUp()` + +Override this to define how to set up the environment. + +##### TearDown {#Environment::TearDown} + +`virtual void Environment::TearDown()` + +Override this to define how to tear down the environment. + +### ScopedTrace {#ScopedTrace} + +`testing::ScopedTrace` + +An instance of this class causes a trace to be included in every test failure +message generated by code in the scope of the lifetime of the `ScopedTrace` +instance. The effect is undone with the destruction of the instance. + +The `ScopedTrace` constructor has the following form: + +```cpp +template +ScopedTrace(const char* file, int line, const T& message) +``` + +Example usage: + +```cpp +testing::ScopedTrace trace("file.cc", 123, "message"); +``` + +The resulting trace includes the given source file path and line number, and the +given message. The `message` argument can be anything streamable to +`std::ostream`. + +See also [`SCOPED_TRACE`](#SCOPED_TRACE). + +### Test {#Test} + +`testing::Test` + +The abstract class that all tests inherit from. `Test` is not copyable. + +#### Public Methods {#Test-public} + +##### SetUpTestSuite {#Test::SetUpTestSuite} + +`static void Test::SetUpTestSuite()` + +Performs shared setup for all tests in the test suite. GoogleTest calls +`SetUpTestSuite()` before running the first test in the test suite. + +##### TearDownTestSuite {#Test::TearDownTestSuite} + +`static void Test::TearDownTestSuite()` + +Performs shared teardown for all tests in the test suite. GoogleTest calls +`TearDownTestSuite()` after running the last test in the test suite. + +##### HasFatalFailure {#Test::HasFatalFailure} + +`static bool Test::HasFatalFailure()` + +Returns true if and only if the current test has a fatal failure. + +##### HasNonfatalFailure {#Test::HasNonfatalFailure} + +`static bool Test::HasNonfatalFailure()` + +Returns true if and only if the current test has a nonfatal failure. + +##### HasFailure {#Test::HasFailure} + +`static bool Test::HasFailure()` + +Returns true if and only if the current test has any failure, either fatal or +nonfatal. + +##### IsSkipped {#Test::IsSkipped} + +`static bool Test::IsSkipped()` + +Returns true if and only if the current test was skipped. + +##### RecordProperty {#Test::RecordProperty} + +`static void Test::RecordProperty(const std::string& key, const std::string& +value)` \ +`static void Test::RecordProperty(const std::string& key, int value)` + +Logs a property for the current test, test suite, or entire invocation of the +test program. Only the last value for a given key is logged. + +The key must be a valid XML attribute name, and cannot conflict with the ones +already used by GoogleTest (`name`, `file`, `line`, `status`, `time`, +`classname`, `type_param`, and `value_param`). + +`RecordProperty` is `public static` so it can be called from utility functions +that are not members of the test fixture. + +Calls to `RecordProperty` made during the lifespan of the test (from the moment +its constructor starts to the moment its destructor finishes) are output in XML +as attributes of the `` element. Properties recorded from a fixture's +`SetUpTestSuite` or `TearDownTestSuite` methods are logged as attributes of the +corresponding `` element. Calls to `RecordProperty` made in the +global context (before or after invocation of `RUN_ALL_TESTS` or from the +`SetUp`/`TearDown` methods of registered `Environment` objects) are output as +attributes of the `` element. + +#### Protected Methods {#Test-protected} + +##### SetUp {#Test::SetUp} + +`virtual void Test::SetUp()` + +Override this to perform test fixture setup. GoogleTest calls `SetUp()` before +running each individual test. + +##### TearDown {#Test::TearDown} + +`virtual void Test::TearDown()` + +Override this to perform test fixture teardown. GoogleTest calls `TearDown()` +after running each individual test. + +### TestWithParam {#TestWithParam} + +`testing::TestWithParam` + +A convenience class which inherits from both [`Test`](#Test) and +[`WithParamInterface`](#WithParamInterface). + +### TestSuite {#TestSuite} + +Represents a test suite. `TestSuite` is not copyable. + +#### Public Methods {#TestSuite-public} + +##### name {#TestSuite::name} + +`const char* TestSuite::name() const` + +Gets the name of the test suite. + +##### type_param {#TestSuite::type_param} + +`const char* TestSuite::type_param() const` + +Returns the name of the parameter type, or `NULL` if this is not a typed or +type-parameterized test suite. See [Typed Tests](../advanced.md#typed-tests) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests). + +##### should_run {#TestSuite::should_run} + +`bool TestSuite::should_run() const` + +Returns true if any test in this test suite should run. + +##### successful_test_count {#TestSuite::successful_test_count} + +`int TestSuite::successful_test_count() const` + +Gets the number of successful tests in this test suite. + +##### skipped_test_count {#TestSuite::skipped_test_count} + +`int TestSuite::skipped_test_count() const` + +Gets the number of skipped tests in this test suite. + +##### failed_test_count {#TestSuite::failed_test_count} + +`int TestSuite::failed_test_count() const` + +Gets the number of failed tests in this test suite. + +##### reportable_disabled_test_count {#TestSuite::reportable_disabled_test_count} + +`int TestSuite::reportable_disabled_test_count() const` + +Gets the number of disabled tests that will be reported in the XML report. + +##### disabled_test_count {#TestSuite::disabled_test_count} + +`int TestSuite::disabled_test_count() const` + +Gets the number of disabled tests in this test suite. + +##### reportable_test_count {#TestSuite::reportable_test_count} + +`int TestSuite::reportable_test_count() const` + +Gets the number of tests to be printed in the XML report. + +##### test_to_run_count {#TestSuite::test_to_run_count} + +`int TestSuite::test_to_run_count() const` + +Get the number of tests in this test suite that should run. + +##### total_test_count {#TestSuite::total_test_count} + +`int TestSuite::total_test_count() const` + +Gets the number of all tests in this test suite. + +##### Passed {#TestSuite::Passed} + +`bool TestSuite::Passed() const` + +Returns true if and only if the test suite passed. + +##### Failed {#TestSuite::Failed} + +`bool TestSuite::Failed() const` + +Returns true if and only if the test suite failed. + +##### elapsed_time {#TestSuite::elapsed_time} + +`TimeInMillis TestSuite::elapsed_time() const` + +Returns the elapsed time, in milliseconds. + +##### start_timestamp {#TestSuite::start_timestamp} + +`TimeInMillis TestSuite::start_timestamp() const` + +Gets the time of the test suite start, in ms from the start of the UNIX epoch. + +##### GetTestInfo {#TestSuite::GetTestInfo} + +`const TestInfo* TestSuite::GetTestInfo(int i) const` + +Returns the [`TestInfo`](#TestInfo) for the `i`-th test among all the tests. `i` +can range from 0 to `total_test_count() - 1`. If `i` is not in that range, +returns `NULL`. + +##### ad_hoc_test_result {#TestSuite::ad_hoc_test_result} + +`const TestResult& TestSuite::ad_hoc_test_result() const` + +Returns the [`TestResult`](#TestResult) that holds test properties recorded +during execution of `SetUpTestSuite` and `TearDownTestSuite`. + +### TestInfo {#TestInfo} + +`testing::TestInfo` + +Stores information about a test. + +#### Public Methods {#TestInfo-public} + +##### test_suite_name {#TestInfo::test_suite_name} + +`const char* TestInfo::test_suite_name() const` + +Returns the test suite name. + +##### name {#TestInfo::name} + +`const char* TestInfo::name() const` + +Returns the test name. + +##### type_param {#TestInfo::type_param} + +`const char* TestInfo::type_param() const` + +Returns the name of the parameter type, or `NULL` if this is not a typed or +type-parameterized test. See [Typed Tests](../advanced.md#typed-tests) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests). + +##### value_param {#TestInfo::value_param} + +`const char* TestInfo::value_param() const` + +Returns the text representation of the value parameter, or `NULL` if this is not +a value-parameterized test. See +[Value-Parameterized Tests](../advanced.md#value-parameterized-tests). + +##### file {#TestInfo::file} + +`const char* TestInfo::file() const` + +Returns the file name where this test is defined. + +##### line {#TestInfo::line} + +`int TestInfo::line() const` + +Returns the line where this test is defined. + +##### is_in_another_shard {#TestInfo::is_in_another_shard} + +`bool TestInfo::is_in_another_shard() const` + +Returns true if this test should not be run because it's in another shard. + +##### should_run {#TestInfo::should_run} + +`bool TestInfo::should_run() const` + +Returns true if this test should run, that is if the test is not disabled (or it +is disabled but the `also_run_disabled_tests` flag has been specified) and its +full name matches the user-specified filter. + +GoogleTest allows the user to filter the tests by their full names. Only the +tests that match the filter will run. See +[Running a Subset of the Tests](../advanced.md#running-a-subset-of-the-tests) +for more information. + +##### is_reportable {#TestInfo::is_reportable} + +`bool TestInfo::is_reportable() const` + +Returns true if and only if this test will appear in the XML report. + +##### result {#TestInfo::result} + +`const TestResult* TestInfo::result() const` + +Returns the result of the test. See [`TestResult`](#TestResult). + +### TestParamInfo {#TestParamInfo} + +`testing::TestParamInfo` + +Describes a parameter to a value-parameterized test. The type `T` is the type of +the parameter. + +Contains the fields `param` and `index` which hold the value of the parameter +and its integer index respectively. + +### UnitTest {#UnitTest} + +`testing::UnitTest` + +This class contains information about the test program. + +`UnitTest` is a singleton class. The only instance is created when +`UnitTest::GetInstance()` is first called. This instance is never deleted. + +`UnitTest` is not copyable. + +#### Public Methods {#UnitTest-public} + +##### GetInstance {#UnitTest::GetInstance} + +`static UnitTest* UnitTest::GetInstance()` + +Gets the singleton `UnitTest` object. The first time this method is called, a +`UnitTest` object is constructed and returned. Consecutive calls will return the +same object. + +##### original_working_dir {#UnitTest::original_working_dir} + +`const char* UnitTest::original_working_dir() const` + +Returns the working directory when the first [`TEST()`](#TEST) or +[`TEST_F()`](#TEST_F) was executed. The `UnitTest` object owns the string. + +##### current_test_suite {#UnitTest::current_test_suite} + +`const TestSuite* UnitTest::current_test_suite() const` + +Returns the [`TestSuite`](#TestSuite) object for the test that's currently +running, or `NULL` if no test is running. + +##### current_test_info {#UnitTest::current_test_info} + +`const TestInfo* UnitTest::current_test_info() const` + +Returns the [`TestInfo`](#TestInfo) object for the test that's currently +running, or `NULL` if no test is running. + +##### random_seed {#UnitTest::random_seed} + +`int UnitTest::random_seed() const` + +Returns the random seed used at the start of the current test run. + +##### successful_test_suite_count {#UnitTest::successful_test_suite_count} + +`int UnitTest::successful_test_suite_count() const` + +Gets the number of successful test suites. + +##### failed_test_suite_count {#UnitTest::failed_test_suite_count} + +`int UnitTest::failed_test_suite_count() const` + +Gets the number of failed test suites. + +##### total_test_suite_count {#UnitTest::total_test_suite_count} + +`int UnitTest::total_test_suite_count() const` + +Gets the number of all test suites. + +##### test_suite_to_run_count {#UnitTest::test_suite_to_run_count} + +`int UnitTest::test_suite_to_run_count() const` + +Gets the number of all test suites that contain at least one test that should +run. + +##### successful_test_count {#UnitTest::successful_test_count} + +`int UnitTest::successful_test_count() const` + +Gets the number of successful tests. + +##### skipped_test_count {#UnitTest::skipped_test_count} + +`int UnitTest::skipped_test_count() const` + +Gets the number of skipped tests. + +##### failed_test_count {#UnitTest::failed_test_count} + +`int UnitTest::failed_test_count() const` + +Gets the number of failed tests. + +##### reportable_disabled_test_count {#UnitTest::reportable_disabled_test_count} + +`int UnitTest::reportable_disabled_test_count() const` + +Gets the number of disabled tests that will be reported in the XML report. + +##### disabled_test_count {#UnitTest::disabled_test_count} + +`int UnitTest::disabled_test_count() const` + +Gets the number of disabled tests. + +##### reportable_test_count {#UnitTest::reportable_test_count} + +`int UnitTest::reportable_test_count() const` + +Gets the number of tests to be printed in the XML report. + +##### total_test_count {#UnitTest::total_test_count} + +`int UnitTest::total_test_count() const` + +Gets the number of all tests. + +##### test_to_run_count {#UnitTest::test_to_run_count} + +`int UnitTest::test_to_run_count() const` + +Gets the number of tests that should run. + +##### start_timestamp {#UnitTest::start_timestamp} + +`TimeInMillis UnitTest::start_timestamp() const` + +Gets the time of the test program start, in ms from the start of the UNIX epoch. + +##### elapsed_time {#UnitTest::elapsed_time} + +`TimeInMillis UnitTest::elapsed_time() const` + +Gets the elapsed time, in milliseconds. + +##### Passed {#UnitTest::Passed} + +`bool UnitTest::Passed() const` + +Returns true if and only if the unit test passed (i.e. all test suites passed). + +##### Failed {#UnitTest::Failed} + +`bool UnitTest::Failed() const` + +Returns true if and only if the unit test failed (i.e. some test suite failed or +something outside of all tests failed). + +##### GetTestSuite {#UnitTest::GetTestSuite} + +`const TestSuite* UnitTest::GetTestSuite(int i) const` + +Gets the [`TestSuite`](#TestSuite) object for the `i`-th test suite among all +the test suites. `i` can range from 0 to `total_test_suite_count() - 1`. If `i` +is not in that range, returns `NULL`. + +##### ad_hoc_test_result {#UnitTest::ad_hoc_test_result} + +`const TestResult& UnitTest::ad_hoc_test_result() const` + +Returns the [`TestResult`](#TestResult) containing information on test failures +and properties logged outside of individual test suites. + +##### listeners {#UnitTest::listeners} + +`TestEventListeners& UnitTest::listeners()` + +Returns the list of event listeners that can be used to track events inside +GoogleTest. See [`TestEventListeners`](#TestEventListeners). + +### TestEventListener {#TestEventListener} + +`testing::TestEventListener` + +The interface for tracing execution of tests. The methods below are listed in +the order the corresponding events are fired. + +#### Public Methods {#TestEventListener-public} + +##### OnTestProgramStart {#TestEventListener::OnTestProgramStart} + +`virtual void TestEventListener::OnTestProgramStart(const UnitTest& unit_test)` + +Fired before any test activity starts. + +##### OnTestIterationStart {#TestEventListener::OnTestIterationStart} + +`virtual void TestEventListener::OnTestIterationStart(const UnitTest& unit_test, +int iteration)` + +Fired before each iteration of tests starts. There may be more than one +iteration if `GTEST_FLAG(repeat)` is set. `iteration` is the iteration index, +starting from 0. + +##### OnEnvironmentsSetUpStart {#TestEventListener::OnEnvironmentsSetUpStart} + +`virtual void TestEventListener::OnEnvironmentsSetUpStart(const UnitTest& +unit_test)` + +Fired before environment set-up for each iteration of tests starts. + +##### OnEnvironmentsSetUpEnd {#TestEventListener::OnEnvironmentsSetUpEnd} + +`virtual void TestEventListener::OnEnvironmentsSetUpEnd(const UnitTest& +unit_test)` + +Fired after environment set-up for each iteration of tests ends. + +##### OnTestSuiteStart {#TestEventListener::OnTestSuiteStart} + +`virtual void TestEventListener::OnTestSuiteStart(const TestSuite& test_suite)` + +Fired before the test suite starts. + +##### OnTestStart {#TestEventListener::OnTestStart} + +`virtual void TestEventListener::OnTestStart(const TestInfo& test_info)` + +Fired before the test starts. + +##### OnTestPartResult {#TestEventListener::OnTestPartResult} + +`virtual void TestEventListener::OnTestPartResult(const TestPartResult& +test_part_result)` + +Fired after a failed assertion or a `SUCCEED()` invocation. If you want to throw +an exception from this function to skip to the next test, it must be an +[`AssertionException`](#AssertionException) or inherited from it. + +##### OnTestEnd {#TestEventListener::OnTestEnd} + +`virtual void TestEventListener::OnTestEnd(const TestInfo& test_info)` + +Fired after the test ends. + +##### OnTestSuiteEnd {#TestEventListener::OnTestSuiteEnd} + +`virtual void TestEventListener::OnTestSuiteEnd(const TestSuite& test_suite)` + +Fired after the test suite ends. + +##### OnEnvironmentsTearDownStart {#TestEventListener::OnEnvironmentsTearDownStart} + +`virtual void TestEventListener::OnEnvironmentsTearDownStart(const UnitTest& +unit_test)` + +Fired before environment tear-down for each iteration of tests starts. + +##### OnEnvironmentsTearDownEnd {#TestEventListener::OnEnvironmentsTearDownEnd} + +`virtual void TestEventListener::OnEnvironmentsTearDownEnd(const UnitTest& +unit_test)` + +Fired after environment tear-down for each iteration of tests ends. + +##### OnTestIterationEnd {#TestEventListener::OnTestIterationEnd} + +`virtual void TestEventListener::OnTestIterationEnd(const UnitTest& unit_test, +int iteration)` + +Fired after each iteration of tests finishes. + +##### OnTestProgramEnd {#TestEventListener::OnTestProgramEnd} + +`virtual void TestEventListener::OnTestProgramEnd(const UnitTest& unit_test)` + +Fired after all test activities have ended. + +### TestEventListeners {#TestEventListeners} + +`testing::TestEventListeners` + +Lets users add listeners to track events in GoogleTest. + +#### Public Methods {#TestEventListeners-public} + +##### Append {#TestEventListeners::Append} + +`void TestEventListeners::Append(TestEventListener* listener)` + +Appends an event listener to the end of the list. GoogleTest assumes ownership +of the listener (i.e. it will delete the listener when the test program +finishes). + +##### Release {#TestEventListeners::Release} + +`TestEventListener* TestEventListeners::Release(TestEventListener* listener)` + +Removes the given event listener from the list and returns it. It then becomes +the caller's responsibility to delete the listener. Returns `NULL` if the +listener is not found in the list. + +##### default_result_printer {#TestEventListeners::default_result_printer} + +`TestEventListener* TestEventListeners::default_result_printer() const` + +Returns the standard listener responsible for the default console output. Can be +removed from the listeners list to shut down default console output. Note that +removing this object from the listener list with +[`Release()`](#TestEventListeners::Release) transfers its ownership to the +caller and makes this function return `NULL` the next time. + +##### default_xml_generator {#TestEventListeners::default_xml_generator} + +`TestEventListener* TestEventListeners::default_xml_generator() const` + +Returns the standard listener responsible for the default XML output controlled +by the `--gtest_output=xml` flag. Can be removed from the listeners list by +users who want to shut down the default XML output controlled by this flag and +substitute it with custom one. Note that removing this object from the listener +list with [`Release()`](#TestEventListeners::Release) transfers its ownership to +the caller and makes this function return `NULL` the next time. + +### TestPartResult {#TestPartResult} + +`testing::TestPartResult` + +A copyable object representing the result of a test part (i.e. an assertion or +an explicit `FAIL()`, `ADD_FAILURE()`, or `SUCCESS()`). + +#### Public Methods {#TestPartResult-public} + +##### type {#TestPartResult::type} + +`Type TestPartResult::type() const` + +Gets the outcome of the test part. + +The return type `Type` is an enum defined as follows: + +```cpp +enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure, // Failed and the test should be terminated. + kSkip // Skipped. +}; +``` + +##### file_name {#TestPartResult::file_name} + +`const char* TestPartResult::file_name() const` + +Gets the name of the source file where the test part took place, or `NULL` if +it's unknown. + +##### line_number {#TestPartResult::line_number} + +`int TestPartResult::line_number() const` + +Gets the line in the source file where the test part took place, or `-1` if it's +unknown. + +##### summary {#TestPartResult::summary} + +`const char* TestPartResult::summary() const` + +Gets the summary of the failure message. + +##### message {#TestPartResult::message} + +`const char* TestPartResult::message() const` + +Gets the message associated with the test part. + +##### skipped {#TestPartResult::skipped} + +`bool TestPartResult::skipped() const` + +Returns true if and only if the test part was skipped. + +##### passed {#TestPartResult::passed} + +`bool TestPartResult::passed() const` + +Returns true if and only if the test part passed. + +##### nonfatally_failed {#TestPartResult::nonfatally_failed} + +`bool TestPartResult::nonfatally_failed() const` + +Returns true if and only if the test part non-fatally failed. + +##### fatally_failed {#TestPartResult::fatally_failed} + +`bool TestPartResult::fatally_failed() const` + +Returns true if and only if the test part fatally failed. + +##### failed {#TestPartResult::failed} + +`bool TestPartResult::failed() const` + +Returns true if and only if the test part failed. + +### TestProperty {#TestProperty} + +`testing::TestProperty` + +A copyable object representing a user-specified test property which can be +output as a key/value string pair. + +#### Public Methods {#TestProperty-public} + +##### key {#key} + +`const char* key() const` + +Gets the user-supplied key. + +##### value {#value} + +`const char* value() const` + +Gets the user-supplied value. + +##### SetValue {#SetValue} + +`void SetValue(const std::string& new_value)` + +Sets a new value, overriding the previous one. + +### TestResult {#TestResult} + +`testing::TestResult` + +Contains information about the result of a single test. + +`TestResult` is not copyable. + +#### Public Methods {#TestResult-public} + +##### total_part_count {#TestResult::total_part_count} + +`int TestResult::total_part_count() const` + +Gets the number of all test parts. This is the sum of the number of successful +test parts and the number of failed test parts. + +##### test_property_count {#TestResult::test_property_count} + +`int TestResult::test_property_count() const` + +Returns the number of test properties. + +##### Passed {#TestResult::Passed} + +`bool TestResult::Passed() const` + +Returns true if and only if the test passed (i.e. no test part failed). + +##### Skipped {#TestResult::Skipped} + +`bool TestResult::Skipped() const` + +Returns true if and only if the test was skipped. + +##### Failed {#TestResult::Failed} + +`bool TestResult::Failed() const` + +Returns true if and only if the test failed. + +##### HasFatalFailure {#TestResult::HasFatalFailure} + +`bool TestResult::HasFatalFailure() const` + +Returns true if and only if the test fatally failed. + +##### HasNonfatalFailure {#TestResult::HasNonfatalFailure} + +`bool TestResult::HasNonfatalFailure() const` + +Returns true if and only if the test has a non-fatal failure. + +##### elapsed_time {#TestResult::elapsed_time} + +`TimeInMillis TestResult::elapsed_time() const` + +Returns the elapsed time, in milliseconds. + +##### start_timestamp {#TestResult::start_timestamp} + +`TimeInMillis TestResult::start_timestamp() const` + +Gets the time of the test case start, in ms from the start of the UNIX epoch. + +##### GetTestPartResult {#TestResult::GetTestPartResult} + +`const TestPartResult& TestResult::GetTestPartResult(int i) const` + +Returns the [`TestPartResult`](#TestPartResult) for the `i`-th test part result +among all the results. `i` can range from 0 to `total_part_count() - 1`. If `i` +is not in that range, aborts the program. + +##### GetTestProperty {#TestResult::GetTestProperty} + +`const TestProperty& TestResult::GetTestProperty(int i) const` + +Returns the [`TestProperty`](#TestProperty) object for the `i`-th test property. +`i` can range from 0 to `test_property_count() - 1`. If `i` is not in that +range, aborts the program. + +### TimeInMillis {#TimeInMillis} + +`testing::TimeInMillis` + +An integer type representing time in milliseconds. + +### Types {#Types} + +`testing::Types` + +Represents a list of types for use in typed tests and type-parameterized tests. + +The template argument `T...` can be any number of types, for example: + +``` +testing::Types +``` + +See [Typed Tests](../advanced.md#typed-tests) and +[Type-Parameterized Tests](../advanced.md#type-parameterized-tests) for more +information. + +### WithParamInterface {#WithParamInterface} + +`testing::WithParamInterface` + +The pure interface class that all value-parameterized tests inherit from. + +A value-parameterized test fixture class must inherit from both [`Test`](#Test) +and `WithParamInterface`. In most cases that just means inheriting from +[`TestWithParam`](#TestWithParam), but more complicated test hierarchies may +need to inherit from `Test` and `WithParamInterface` at different levels. + +This interface defines the type alias `ParamType` for the parameter type `T` and +has support for accessing the test parameter value via the `GetParam()` method: + +``` +static const ParamType& GetParam() +``` + +For more information, see +[Value-Parameterized Tests](../advanced.md#value-parameterized-tests). + +## Functions + +GoogleTest defines the following functions to help with writing and running +tests. + +### InitGoogleTest {#InitGoogleTest} + +`void testing::InitGoogleTest(int* argc, char** argv)` \ +`void testing::InitGoogleTest(int* argc, wchar_t** argv)` \ +`void testing::InitGoogleTest()` + +Initializes GoogleTest. This must be called before calling +[`RUN_ALL_TESTS()`](#RUN_ALL_TESTS). In particular, it parses the command line +for the flags that GoogleTest recognizes. Whenever a GoogleTest flag is seen, it +is removed from `argv`, and `*argc` is decremented. + +No value is returned. Instead, the GoogleTest flag variables are updated. + +The `InitGoogleTest(int* argc, wchar_t** argv)` overload can be used in Windows +programs compiled in `UNICODE` mode. + +The argument-less `InitGoogleTest()` overload can be used on Arduino/embedded +platforms where there is no `argc`/`argv`. + +### AddGlobalTestEnvironment {#AddGlobalTestEnvironment} + +`Environment* testing::AddGlobalTestEnvironment(Environment* env)` + +Adds a test environment to the test program. Must be called before +[`RUN_ALL_TESTS()`](#RUN_ALL_TESTS) is called. See +[Global Set-Up and Tear-Down](../advanced.md#global-set-up-and-tear-down) for +more information. + +See also [`Environment`](#Environment). + +### RegisterTest {#RegisterTest} + +```cpp +template +TestInfo* testing::RegisterTest(const char* test_suite_name, const char* test_name, + const char* type_param, const char* value_param, + const char* file, int line, Factory factory) +``` + +Dynamically registers a test with the framework. + +The `factory` argument is a factory callable (move-constructible) object or +function pointer that creates a new instance of the `Test` object. It handles +ownership to the caller. The signature of the callable is `Fixture*()`, where +`Fixture` is the test fixture class for the test. All tests registered with the +same `test_suite_name` must return the same fixture type. This is checked at +runtime. + +The framework will infer the fixture class from the factory and will call the +`SetUpTestSuite` and `TearDownTestSuite` methods for it. + +Must be called before [`RUN_ALL_TESTS()`](#RUN_ALL_TESTS) is invoked, otherwise +behavior is undefined. + +See +[Registering tests programmatically](../advanced.md#registering-tests-programmatically) +for more information. + +### RUN_ALL_TESTS {#RUN_ALL_TESTS} + +`int RUN_ALL_TESTS()` + +Use this function in `main()` to run all tests. It returns `0` if all tests are +successful, or `1` otherwise. + +`RUN_ALL_TESTS()` should be invoked after the command line has been parsed by +[`InitGoogleTest()`](#InitGoogleTest). + +This function was formerly a macro; thus, it is in the global namespace and has +an all-caps name. + +### AssertionSuccess {#AssertionSuccess} + +`AssertionResult testing::AssertionSuccess()` + +Creates a successful assertion result. See +[`AssertionResult`](#AssertionResult). + +### AssertionFailure {#AssertionFailure} + +`AssertionResult testing::AssertionFailure()` + +Creates a failed assertion result. Use the `<<` operator to store a failure +message: + +```cpp +testing::AssertionFailure() << "My failure message"; +``` + +See [`AssertionResult`](#AssertionResult). + +### StaticAssertTypeEq {#StaticAssertTypeEq} + +`testing::StaticAssertTypeEq()` + +Compile-time assertion for type equality. Compiles if and only if `T1` and `T2` +are the same type. The value it returns is irrelevant. + +See [Type Assertions](../advanced.md#type-assertions) for more information. + +### PrintToString {#PrintToString} + +`std::string testing::PrintToString(x)` + +Prints any value `x` using GoogleTest's value printer. + +See +[Teaching GoogleTest How to Print Your Values](../advanced.md#teaching-googletest-how-to-print-your-values) +for more information. + +### PrintToStringParamName {#PrintToStringParamName} + +`std::string testing::PrintToStringParamName(TestParamInfo& info)` + +A built-in parameterized test name generator which returns the result of +[`PrintToString`](#PrintToString) called on `info.param`. Does not work when the +test parameter is a `std::string` or C string. See +[Specifying Names for Value-Parameterized Test Parameters](../advanced.md#specifying-names-for-value-parameterized-test-parameters) +for more information. + +See also [`TestParamInfo`](#TestParamInfo) and +[`INSTANTIATE_TEST_SUITE_P`](#INSTANTIATE_TEST_SUITE_P). diff --git a/Engine/lib/assimp/contrib/googletest/docs/samples.md b/Engine/lib/assimp/contrib/googletest/docs/samples.md new file mode 100644 index 000000000..dedc59098 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/docs/samples.md @@ -0,0 +1,22 @@ +# Googletest Samples + +If you're like us, you'd like to look at +[googletest samples.](https://github.com/google/googletest/blob/main/googletest/samples) +The sample directory has a number of well-commented samples showing how to use a +variety of googletest features. + +* Sample #1 shows the basic steps of using googletest to test C++ functions. +* Sample #2 shows a more complex unit test for a class with multiple member + functions. +* Sample #3 uses a test fixture. +* Sample #4 teaches you how to use googletest and `googletest.h` together to + get the best of both libraries. +* Sample #5 puts shared testing logic in a base test fixture, and reuses it in + derived fixtures. +* Sample #6 demonstrates type-parameterized tests. +* Sample #7 teaches the basics of value-parameterized tests. +* Sample #8 shows using `Combine()` in value-parameterized tests. +* Sample #9 shows use of the listener API to modify Google Test's console + output and the use of its reflection API to inspect test results. +* Sample #10 shows use of the listener API to implement a primitive memory + leak checker. diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/CMakeLists.txt b/Engine/lib/assimp/contrib/googletest/googlemock/CMakeLists.txt new file mode 100644 index 000000000..a9aa0723f --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/CMakeLists.txt @@ -0,0 +1,209 @@ +######################################################################## +# Note: CMake support is community-based. The maintainers do not use CMake +# internally. +# +# CMake build script for Google Mock. +# +# To run the tests for Google Mock itself on Linux, use 'make test' or +# ctest. You can select which tests to run using 'ctest -R regex'. +# For more options, run 'ctest --help'. + +option(gmock_build_tests "Build all of Google Mock's own tests." OFF) + +# A directory to find Google Test sources. +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gtest/CMakeLists.txt") + set(gtest_dir gtest) +else() + set(gtest_dir ../googletest) +endif() + +# Defines pre_project_set_up_hermetic_build() and set_up_hermetic_build(). +include("${gtest_dir}/cmake/hermetic_build.cmake" OPTIONAL) + +if (COMMAND pre_project_set_up_hermetic_build) + # Google Test also calls hermetic setup functions from add_subdirectory, + # although its changes will not affect things at the current scope. + pre_project_set_up_hermetic_build() +endif() + +######################################################################## +# +# Project-wide settings + +# Name of the project. +# +# CMake files in this project can refer to the root source directory +# as ${gmock_SOURCE_DIR} and to the root binary directory as +# ${gmock_BINARY_DIR}. +# Language "C" is required for find_package(Threads). +cmake_minimum_required(VERSION 3.13) +project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) + +if (COMMAND set_up_hermetic_build) + set_up_hermetic_build() +endif() + +# Instructs CMake to process Google Test's CMakeLists.txt and add its +# targets to the current scope. We are placing Google Test's binary +# directory in a subdirectory of our own as VC compilation may break +# if they are the same (the default). +add_subdirectory("${gtest_dir}" "${gmock_BINARY_DIR}/${gtest_dir}") + + +# These commands only run if this is the main project +if(CMAKE_PROJECT_NAME STREQUAL "gmock" OR CMAKE_PROJECT_NAME STREQUAL "googletest-distribution") + # BUILD_SHARED_LIBS is a standard CMake variable, but we declare it here to + # make it prominent in the GUI. + option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF) +else() + mark_as_advanced(gmock_build_tests) +endif() + +# Although Google Test's CMakeLists.txt calls this function, the +# changes there don't affect the current scope. Therefore we have to +# call it again here. +config_compiler_and_linker() # from ${gtest_dir}/cmake/internal_utils.cmake + +# Adds Google Mock's and Google Test's header directories to the search path. +set(gmock_build_include_dirs + "${gmock_SOURCE_DIR}/include" + "${gmock_SOURCE_DIR}" + "${gtest_SOURCE_DIR}/include" + # This directory is needed to build directly from Google Test sources. + "${gtest_SOURCE_DIR}") +include_directories(${gmock_build_include_dirs}) + +######################################################################## +# +# Defines the gmock & gmock_main libraries. User tests should link +# with one of them. + +# Google Mock libraries. We build them using more strict warnings than what +# are used for other targets, to ensure that Google Mock can be compiled by +# a user aggressive about warnings. +if (MSVC) + cxx_library(gmock + "${cxx_strict}" + "${gtest_dir}/src/gtest-all.cc" + src/gmock-all.cc) + + cxx_library(gmock_main + "${cxx_strict}" + "${gtest_dir}/src/gtest-all.cc" + src/gmock-all.cc + src/gmock_main.cc) +else() + cxx_library(gmock "${cxx_strict}" src/gmock-all.cc) + target_link_libraries(gmock PUBLIC gtest) + set_target_properties(gmock PROPERTIES VERSION ${GOOGLETEST_VERSION}) + cxx_library(gmock_main "${cxx_strict}" src/gmock_main.cc) + target_link_libraries(gmock_main PUBLIC gmock) + set_target_properties(gmock_main PROPERTIES VERSION ${GOOGLETEST_VERSION}) +endif() + +string(REPLACE ";" "$" dirs "${gmock_build_include_dirs}") +target_include_directories(gmock SYSTEM INTERFACE + "$" + "$/${CMAKE_INSTALL_INCLUDEDIR}>") +target_include_directories(gmock_main SYSTEM INTERFACE + "$" + "$/${CMAKE_INSTALL_INCLUDEDIR}>") + +######################################################################## +# +# Install rules +install_project(gmock gmock_main) + +######################################################################## +# +# Google Mock's own tests. +# +# You can skip this section if you aren't interested in testing +# Google Mock itself. +# +# The tests are not built by default. To build them, set the +# gmock_build_tests option to ON. You can do it by running ccmake +# or specifying the -Dgmock_build_tests=ON flag when running cmake. + +if (gmock_build_tests) + # This must be set in the root directory for the tests to be run by + # 'make test' or ctest. + enable_testing() + + if (MINGW OR CYGWIN) + add_compile_options("-Wa,-mbig-obj") + endif() + + ############################################################ + # C++ tests built with standard compiler flags. + + cxx_test(gmock-actions_test gmock_main) + cxx_test(gmock-cardinalities_test gmock_main) + cxx_test(gmock_ex_test gmock_main) + cxx_test(gmock-function-mocker_test gmock_main) + cxx_test(gmock-internal-utils_test gmock_main) + cxx_test(gmock-matchers-arithmetic_test gmock_main) + cxx_test(gmock-matchers-comparisons_test gmock_main) + cxx_test(gmock-matchers-containers_test gmock_main) + cxx_test(gmock-matchers-misc_test gmock_main) + cxx_test(gmock-more-actions_test gmock_main) + cxx_test(gmock-nice-strict_test gmock_main) + cxx_test(gmock-port_test gmock_main) + cxx_test(gmock-spec-builders_test gmock_main) + cxx_test(gmock_link_test gmock_main test/gmock_link2_test.cc) + cxx_test(gmock_test gmock_main) + + if (DEFINED GTEST_HAS_PTHREAD) + cxx_test(gmock_stress_test gmock) + endif() + + # gmock_all_test is commented to save time building and running tests. + # Uncomment if necessary. + # cxx_test(gmock_all_test gmock_main) + + ############################################################ + # C++ tests built with non-standard compiler flags. + + if (MSVC) + cxx_library(gmock_main_no_exception "${cxx_no_exception}" + "${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc) + + cxx_library(gmock_main_no_rtti "${cxx_no_rtti}" + "${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc) + + else() + cxx_library(gmock_main_no_exception "${cxx_no_exception}" src/gmock_main.cc) + target_link_libraries(gmock_main_no_exception PUBLIC gmock) + + cxx_library(gmock_main_no_rtti "${cxx_no_rtti}" src/gmock_main.cc) + target_link_libraries(gmock_main_no_rtti PUBLIC gmock) + endif() + cxx_test_with_flags(gmock-more-actions_no_exception_test "${cxx_no_exception}" + gmock_main_no_exception test/gmock-more-actions_test.cc) + + cxx_test_with_flags(gmock_no_rtti_test "${cxx_no_rtti}" + gmock_main_no_rtti test/gmock-spec-builders_test.cc) + + cxx_shared_library(shared_gmock_main "${cxx_default}" + "${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc) + + # Tests that a binary can be built with Google Mock as a shared library. On + # some system configurations, it may not possible to run the binary without + # knowing more details about the system configurations. We do not try to run + # this binary. To get a more robust shared library coverage, configure with + # -DBUILD_SHARED_LIBS=ON. + cxx_executable_with_flags(shared_gmock_test_ "${cxx_default}" + shared_gmock_main test/gmock-spec-builders_test.cc) + set_target_properties(shared_gmock_test_ + PROPERTIES + COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1") + + ############################################################ + # Python tests. + + cxx_executable(gmock_leak_test_ test gmock_main) + py_test(gmock_leak_test) + + cxx_executable(gmock_output_test_ test gmock) + py_test(gmock_output_test) +endif() diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/README.md b/Engine/lib/assimp/contrib/googletest/googlemock/README.md new file mode 100644 index 000000000..7da60655d --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/README.md @@ -0,0 +1,40 @@ +# Googletest Mocking (gMock) Framework + +### Overview + +Google's framework for writing and using C++ mock classes. It can help you +derive better designs of your system and write better tests. + +It is inspired by: + +* [jMock](http://www.jmock.org/) +* [EasyMock](http://www.easymock.org/) +* [Hamcrest](http://code.google.com/p/hamcrest/) + +It is designed with C++'s specifics in mind. + +gMock: + +- Provides a declarative syntax for defining mocks. +- Can define partial (hybrid) mocks, which are a cross of real and mock + objects. +- Handles functions of arbitrary types and overloaded functions. +- Comes with a rich set of matchers for validating function arguments. +- Uses an intuitive syntax for controlling the behavior of a mock. +- Does automatic verification of expectations (no record-and-replay needed). +- Allows arbitrary (partial) ordering constraints on function calls to be + expressed. +- Lets a user extend it by defining new matchers and actions. +- Does not use exceptions. +- Is easy to learn and use. + +Details and examples can be found here: + +* [gMock for Dummies](https://google.github.io/googletest/gmock_for_dummies.html) +* [Legacy gMock FAQ](https://google.github.io/googletest/gmock_faq.html) +* [gMock Cookbook](https://google.github.io/googletest/gmock_cook_book.html) +* [gMock Cheat Sheet](https://google.github.io/googletest/gmock_cheat_sheet.html) + +GoogleMock is a part of +[GoogleTest C++ testing framework](http://github.com/google/googletest/) and a +subject to the same requirements. diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock.pc.in b/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock.pc.in new file mode 100644 index 000000000..23c67b5c8 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock.pc.in @@ -0,0 +1,10 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: gmock +Description: GoogleMock (without main() function) +Version: @PROJECT_VERSION@ +URL: https://github.com/google/googletest +Requires: gtest = @PROJECT_VERSION@ +Libs: -L${libdir} -lgmock @CMAKE_THREAD_LIBS_INIT@ +Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@ diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock_main.pc.in b/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock_main.pc.in new file mode 100644 index 000000000..66ffea7f4 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/cmake/gmock_main.pc.in @@ -0,0 +1,10 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: gmock_main +Description: GoogleMock (with main() function) +Version: @PROJECT_VERSION@ +URL: https://github.com/google/googletest +Requires: gmock = @PROJECT_VERSION@ +Libs: -L${libdir} -lgmock_main @CMAKE_THREAD_LIBS_INIT@ +Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@ diff --git a/Engine/lib/assimp/contrib/gtest/docs/README.md b/Engine/lib/assimp/contrib/googletest/googlemock/docs/README.md similarity index 100% rename from Engine/lib/assimp/contrib/gtest/docs/README.md rename to Engine/lib/assimp/contrib/googletest/googlemock/docs/README.md diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-actions.h b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-actions.h new file mode 100644 index 000000000..bd9ba73ee --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-actions.h @@ -0,0 +1,2297 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// Users can, however, define any local functors (e.g. a lambda) that +// can be used as actions. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' on +// https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ + +#ifndef _WIN32_WCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gmock/internal/gmock-pp.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100) + +namespace testing { + +// To implement an action Foo, define: +// 1. a class FooAction that implements the ActionInterface interface, and +// 2. a factory function that creates an Action object from a +// const FooAction*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Action objects can now be copied like plain values. + +namespace internal { + +// BuiltInDefaultValueGetter::Get() returns a +// default-constructed T value. BuiltInDefaultValueGetter::Get() crashes with an error. +// +// This primary template is used when kDefaultConstructible is true. +template +struct BuiltInDefaultValueGetter { + static T Get() { return T(); } +}; +template +struct BuiltInDefaultValueGetter { + static T Get() { + Assert(false, __FILE__, __LINE__, + "Default action undefined for the function return type."); + return internal::Invalid(); + // The above statement will never be reached, but is required in + // order for this function to compile. + } +}; + +// BuiltInDefaultValue::Get() returns the "built-in" default value +// for type T, which is NULL when T is a raw pointer type, 0 when T is +// a numeric type, false when T is bool, or "" when T is string or +// std::string. In addition, in C++11 and above, it turns a +// default-constructed T value if T is default constructible. For any +// other type T, the built-in default T value is undefined, and the +// function will abort the process. +template +class BuiltInDefaultValue { + public: + // This function returns true if and only if type T has a built-in default + // value. + static bool Exists() { return ::std::is_default_constructible::value; } + + static T Get() { + return BuiltInDefaultValueGetter< + T, ::std::is_default_constructible::value>::Get(); + } +}; + +// This partial specialization says that we use the same built-in +// default value for T and const T. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return BuiltInDefaultValue::Exists(); } + static T Get() { return BuiltInDefaultValue::Get(); } +}; + +// This partial specialization defines the default values for pointer +// types. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return true; } + static T* Get() { return nullptr; } +}; + +// The following specializations define the default values for +// specific types we care about. +#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ + template <> \ + class BuiltInDefaultValue { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ + } + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, ""); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0'); + +// There's no need for a default action for signed wchar_t, as that +// type is the same as wchar_t for gcc, and invalid for MSVC. +// +// There's also no need for a default action for unsigned wchar_t, as +// that type is the same as unsigned int for gcc, and invalid for +// MSVC. +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U); // NOLINT +#endif + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); + +#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ + +// Partial implementations of metaprogramming types from the standard library +// not available in C++11. + +template +struct negation + // NOLINTNEXTLINE + : std::integral_constant {}; + +// Base case: with zero predicates the answer is always true. +template +struct conjunction : std::true_type {}; + +// With a single predicate, the answer is that predicate. +template +struct conjunction : P1 {}; + +// With multiple predicates the answer is the first predicate if that is false, +// and we recurse otherwise. +template +struct conjunction + : std::conditional, P1>::type {}; + +template +struct disjunction : std::false_type {}; + +template +struct disjunction : P1 {}; + +template +struct disjunction + // NOLINTNEXTLINE + : std::conditional, P1>::type {}; + +template +using void_t = void; + +// Detects whether an expression of type `From` can be implicitly converted to +// `To` according to [conv]. In C++17, [conv]/3 defines this as follows: +// +// An expression e can be implicitly converted to a type T if and only if +// the declaration T t=e; is well-formed, for some invented temporary +// variable t ([dcl.init]). +// +// [conv]/2 implies we can use function argument passing to detect whether this +// initialization is valid. +// +// Note that this is distinct from is_convertible, which requires this be valid: +// +// To test() { +// return declval(); +// } +// +// In particular, is_convertible doesn't give the correct answer when `To` and +// `From` are the same non-moveable type since `declval` will be an rvalue +// reference, defeating the guaranteed copy elision that would otherwise make +// this function work. +// +// REQUIRES: `From` is not cv void. +template +struct is_implicitly_convertible { + private: + // A function that accepts a parameter of type T. This can be called with type + // U successfully only if U is implicitly convertible to T. + template + static void Accept(T); + + // A function that creates a value of type T. + template + static T Make(); + + // An overload be selected when implicit conversion from T to To is possible. + template (Make()))> + static std::true_type TestImplicitConversion(int); + + // A fallback overload selected in all other cases. + template + static std::false_type TestImplicitConversion(...); + + public: + using type = decltype(TestImplicitConversion(0)); + static constexpr bool value = type::value; +}; + +// Like std::invoke_result_t from C++17, but works only for objects with call +// operators (not e.g. member function pointers, which we don't need specific +// support for in OnceAction because std::function deals with them). +template +using call_result_t = decltype(std::declval()(std::declval()...)); + +template +struct is_callable_r_impl : std::false_type {}; + +// Specialize the struct for those template arguments where call_result_t is +// well-formed. When it's not, the generic template above is chosen, resulting +// in std::false_type. +template +struct is_callable_r_impl>, R, F, Args...> + : std::conditional< + std::is_void::value, // + std::true_type, // + is_implicitly_convertible, R>>::type {}; + +// Like std::is_invocable_r from C++17, but works only for objects with call +// operators. See the note on call_result_t. +template +using is_callable_r = is_callable_r_impl; + +// Like std::as_const from C++17. +template +typename std::add_const::type& as_const(T& t) { + return t; +} + +} // namespace internal + +// Specialized for function types below. +template +class OnceAction; + +// An action that can only be used once. +// +// This is accepted by WillOnce, which doesn't require the underlying action to +// be copy-constructible (only move-constructible), and promises to invoke it as +// an rvalue reference. This allows the action to work with move-only types like +// std::move_only_function in a type-safe manner. +// +// For example: +// +// // Assume we have some API that needs to accept a unique pointer to some +// // non-copyable object Foo. +// void AcceptUniquePointer(std::unique_ptr foo); +// +// // We can define an action that provides a Foo to that API. Because It +// // has to give away its unique pointer, it must not be called more than +// // once, so its call operator is &&-qualified. +// struct ProvideFoo { +// std::unique_ptr foo; +// +// void operator()() && { +// AcceptUniquePointer(std::move(Foo)); +// } +// }; +// +// // This action can be used with WillOnce. +// EXPECT_CALL(mock, Call) +// .WillOnce(ProvideFoo{std::make_unique(...)}); +// +// // But a call to WillRepeatedly will fail to compile. This is correct, +// // since the action cannot correctly be used repeatedly. +// EXPECT_CALL(mock, Call) +// .WillRepeatedly(ProvideFoo{std::make_unique(...)}); +// +// A less-contrived example would be an action that returns an arbitrary type, +// whose &&-qualified call operator is capable of dealing with move-only types. +template +class OnceAction final { + private: + // True iff we can use the given callable type (or lvalue reference) directly + // via StdFunctionAdaptor. + template + using IsDirectlyCompatible = internal::conjunction< + // It must be possible to capture the callable in StdFunctionAdaptor. + std::is_constructible::type, Callable>, + // The callable must be compatible with our signature. + internal::is_callable_r::type, + Args...>>; + + // True iff we can use the given callable type via StdFunctionAdaptor once we + // ignore incoming arguments. + template + using IsCompatibleAfterIgnoringArguments = internal::conjunction< + // It must be possible to capture the callable in a lambda. + std::is_constructible::type, Callable>, + // The callable must be invocable with zero arguments, returning something + // convertible to Result. + internal::is_callable_r::type>>; + + public: + // Construct from a callable that is directly compatible with our mocked + // signature: it accepts our function type's arguments and returns something + // convertible to our result type. + template ::type>>, + IsDirectlyCompatible> // + ::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + : function_(StdFunctionAdaptor::type>( + {}, std::forward(callable))) {} + + // As above, but for a callable that ignores the mocked function's arguments. + template ::type>>, + // Exclude callables for which the overload above works. + // We'd rather provide the arguments if possible. + internal::negation>, + IsCompatibleAfterIgnoringArguments>::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + // Call the constructor above with a callable + // that ignores the input arguments. + : OnceAction(IgnoreIncomingArguments::type>{ + std::forward(callable)}) {} + + // We are naturally copyable because we store only an std::function, but + // semantically we should not be copyable. + OnceAction(const OnceAction&) = delete; + OnceAction& operator=(const OnceAction&) = delete; + OnceAction(OnceAction&&) = default; + + // Invoke the underlying action callable with which we were constructed, + // handing it the supplied arguments. + Result Call(Args... args) && { + return function_(std::forward(args)...); + } + + private: + // An adaptor that wraps a callable that is compatible with our signature and + // being invoked as an rvalue reference so that it can be used as an + // StdFunctionAdaptor. This throws away type safety, but that's fine because + // this is only used by WillOnce, which we know calls at most once. + // + // Once we have something like std::move_only_function from C++23, we can do + // away with this. + template + class StdFunctionAdaptor final { + public: + // A tag indicating that the (otherwise universal) constructor is accepting + // the callable itself, instead of e.g. stealing calls for the move + // constructor. + struct CallableTag final {}; + + template + explicit StdFunctionAdaptor(CallableTag, F&& callable) + : callable_(std::make_shared(std::forward(callable))) {} + + // Rather than explicitly returning Result, we return whatever the wrapped + // callable returns. This allows for compatibility with existing uses like + // the following, when the mocked function returns void: + // + // EXPECT_CALL(mock_fn_, Call) + // .WillOnce([&] { + // [...] + // return 0; + // }); + // + // Such a callable can be turned into std::function. If we use an + // explicit return type of Result here then it *doesn't* work with + // std::function, because we'll get a "void function should not return a + // value" error. + // + // We need not worry about incompatible result types because the SFINAE on + // OnceAction already checks this for us. std::is_invocable_r_v itself makes + // the same allowance for void result types. + template + internal::call_result_t operator()( + ArgRefs&&... args) const { + return std::move(*callable_)(std::forward(args)...); + } + + private: + // We must put the callable on the heap so that we are copyable, which + // std::function needs. + std::shared_ptr callable_; + }; + + // An adaptor that makes a callable that accepts zero arguments callable with + // our mocked arguments. + template + struct IgnoreIncomingArguments { + internal::call_result_t operator()(Args&&...) { + return std::move(callable)(); + } + + Callable callable; + }; + + std::function function_; +}; + +// When an unexpected function call is encountered, Google Mock will +// let it return a default value if the user has specified one for its +// return type, or if the return type has a built-in default value; +// otherwise Google Mock won't know what value to return and will have +// to abort the process. +// +// The DefaultValue class allows a user to specify the +// default value for a type T that is both copyable and publicly +// destructible (i.e. anything that can be used as a function return +// type). The usage is: +// +// // Sets the default value for type T to be foo. +// DefaultValue::Set(foo); +template +class DefaultValue { + public: + // Sets the default value for type T; requires T to be + // copy-constructable and have a public destructor. + static void Set(T x) { + delete producer_; + producer_ = new FixedValueProducer(x); + } + + // Provides a factory function to be called to generate the default value. + // This method can be used even if T is only move-constructible, but it is not + // limited to that case. + typedef T (*FactoryFunction)(); + static void SetFactory(FactoryFunction factory) { + delete producer_; + producer_ = new FactoryValueProducer(factory); + } + + // Unsets the default value for type T. + static void Clear() { + delete producer_; + producer_ = nullptr; + } + + // Returns true if and only if the user has set the default value for type T. + static bool IsSet() { return producer_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T if the user has set one; + // otherwise returns the built-in default value. Requires that Exists() + // is true, which ensures that the return value is well-defined. + static T Get() { + return producer_ == nullptr ? internal::BuiltInDefaultValue::Get() + : producer_->Produce(); + } + + private: + class ValueProducer { + public: + virtual ~ValueProducer() = default; + virtual T Produce() = 0; + }; + + class FixedValueProducer : public ValueProducer { + public: + explicit FixedValueProducer(T value) : value_(value) {} + T Produce() override { return value_; } + + private: + const T value_; + FixedValueProducer(const FixedValueProducer&) = delete; + FixedValueProducer& operator=(const FixedValueProducer&) = delete; + }; + + class FactoryValueProducer : public ValueProducer { + public: + explicit FactoryValueProducer(FactoryFunction factory) + : factory_(factory) {} + T Produce() override { return factory_(); } + + private: + const FactoryFunction factory_; + FactoryValueProducer(const FactoryValueProducer&) = delete; + FactoryValueProducer& operator=(const FactoryValueProducer&) = delete; + }; + + static ValueProducer* producer_; +}; + +// This partial specialization allows a user to set default values for +// reference types. +template +class DefaultValue { + public: + // Sets the default value for type T&. + static void Set(T& x) { // NOLINT + address_ = &x; + } + + // Unsets the default value for type T&. + static void Clear() { address_ = nullptr; } + + // Returns true if and only if the user has set the default value for type T&. + static bool IsSet() { return address_ != nullptr; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T& if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T& Get() { + return address_ == nullptr ? internal::BuiltInDefaultValue::Get() + : *address_; + } + + private: + static T* address_; +}; + +// This specialization allows DefaultValue::Get() to +// compile. +template <> +class DefaultValue { + public: + static bool Exists() { return true; } + static void Get() {} +}; + +// Points to the user-set default value for type T. +template +typename DefaultValue::ValueProducer* DefaultValue::producer_ = nullptr; + +// Points to the user-set default value for type T&. +template +T* DefaultValue::address_ = nullptr; + +// Implement this interface to define an action for function type F. +template +class ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + ActionInterface() = default; + virtual ~ActionInterface() = default; + + // Performs the action. This method is not const, as in general an + // action can have side effects and be stateful. For example, a + // get-the-next-element-from-the-collection action will need to + // remember the current element. + virtual Result Perform(const ArgumentTuple& args) = 0; + + private: + ActionInterface(const ActionInterface&) = delete; + ActionInterface& operator=(const ActionInterface&) = delete; +}; + +template +class Action; + +// An Action is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function of type +// R(Args...) is called. The implementation of Action is just a +// std::shared_ptr to const ActionInterface. Don't inherit from Action! You +// can view an object implementing ActionInterface as a concrete action +// (including its current state), and an Action object as a handle to it. +template +class Action { + private: + using F = R(Args...); + + // Adapter class to allow constructing Action from a legacy ActionInterface. + // New code should create Actions from functors instead. + struct ActionAdapter { + // Adapter must be copyable to satisfy std::function requirements. + ::std::shared_ptr> impl_; + + template + typename internal::Function::Result operator()(InArgs&&... args) { + return impl_->Perform( + ::std::forward_as_tuple(::std::forward(args)...)); + } + }; + + template + using IsCompatibleFunctor = std::is_constructible, G>; + + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + // Constructs a null Action. Needed for storing Action objects in + // STL containers. + Action() = default; + + // Construct an Action from a specified callable. + // This cannot take std::function directly, because then Action would not be + // directly constructible from lambda (it would require two conversions). + template < + typename G, + typename = typename std::enable_if, std::is_constructible, + G>>::value>::type> + Action(G&& fun) { // NOLINT + Init(::std::forward(fun), IsCompatibleFunctor()); + } + + // Constructs an Action from its implementation. + explicit Action(ActionInterface* impl) + : fun_(ActionAdapter{::std::shared_ptr>(impl)}) {} + + // This constructor allows us to turn an Action object into an + // Action, as long as F's arguments can be implicitly converted + // to Func's and Func's return type can be implicitly converted to F's. + template + Action(const Action& action) // NOLINT + : fun_(action.fun_) {} + + // Returns true if and only if this is the DoDefault() action. + bool IsDoDefault() const { return fun_ == nullptr; } + + // Performs the action. Note that this method is const even though + // the corresponding method in ActionInterface is not. The reason + // is that a const Action means that it cannot be re-bound to + // another concrete action, not that the concrete action it binds to + // cannot change state. (Think of the difference between a const + // pointer and a pointer to const.) + Result Perform(ArgumentTuple args) const { + if (IsDoDefault()) { + internal::IllegalDoDefault(__FILE__, __LINE__); + } + return internal::Apply(fun_, ::std::move(args)); + } + + // An action can be used as a OnceAction, since it's obviously safe to call it + // once. + operator OnceAction() const { // NOLINT + // Return a OnceAction-compatible callable that calls Perform with the + // arguments it is provided. We could instead just return fun_, but then + // we'd need to handle the IsDoDefault() case separately. + struct OA { + Action action; + + R operator()(Args... args) && { + return action.Perform( + std::forward_as_tuple(std::forward(args)...)); + } + }; + + return OA{*this}; + } + + private: + template + friend class Action; + + template + void Init(G&& g, ::std::true_type) { + fun_ = ::std::forward(g); + } + + template + void Init(G&& g, ::std::false_type) { + fun_ = IgnoreArgs::type>{::std::forward(g)}; + } + + template + struct IgnoreArgs { + template + Result operator()(const InArgs&...) const { + return function_impl(); + } + + FunctionImpl function_impl; + }; + + // fun_ is an empty function if and only if this is the DoDefault() action. + ::std::function fun_; +}; + +// The PolymorphicAction class template makes it easy to implement a +// polymorphic action (i.e. an action that can be used in mock +// functions of than one type, e.g. Return()). +// +// To define a polymorphic action, a user first provides a COPYABLE +// implementation class that has a Perform() method template: +// +// class FooAction { +// public: +// template +// Result Perform(const ArgumentTuple& args) const { +// // Processes the arguments and returns a result, using +// // std::get(args) to get the N-th (0-based) argument in the tuple. +// } +// ... +// }; +// +// Then the user creates the polymorphic action using +// MakePolymorphicAction(object) where object has type FooAction. See +// the definition of Return(void) and SetArgumentPointee(value) for +// complete examples. +template +class PolymorphicAction { + public: + explicit PolymorphicAction(const Impl& impl) : impl_(impl) {} + + template + operator Action() const { + return Action(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + Result Perform(const ArgumentTuple& args) override { + return impl_.template Perform(args); + } + + private: + Impl impl_; + }; + + Impl impl_; +}; + +// Creates an Action from its implementation and returns it. The +// created Action object owns the implementation. +template +Action MakeAction(ActionInterface* impl) { + return Action(impl); +} + +// Creates a polymorphic action from its implementation. This is +// easier to use than the PolymorphicAction constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicAction(foo); +// vs +// PolymorphicAction(foo); +template +inline PolymorphicAction MakePolymorphicAction(const Impl& impl) { + return PolymorphicAction(impl); +} + +namespace internal { + +// Helper struct to specialize ReturnAction to execute a move instead of a copy +// on return. Useful for move-only types, but could be used on any type. +template +struct ByMoveWrapper { + explicit ByMoveWrapper(T value) : payload(std::move(value)) {} + T payload; +}; + +// The general implementation of Return(R). Specializations follow below. +template +class ReturnAction final { + public: + explicit ReturnAction(R value) : value_(std::move(value)) {} + + template >, // + negation>, // + std::is_convertible, // + std::is_move_constructible>::value>::type> + operator OnceAction() && { // NOLINT + return Impl(std::move(value_)); + } + + template >, // + negation>, // + std::is_convertible, // + std::is_copy_constructible>::value>::type> + operator Action() const { // NOLINT + return Impl(value_); + } + + private: + // Implements the Return(x) action for a mock function that returns type U. + template + class Impl final { + public: + // The constructor used when the return value is allowed to move from the + // input value (i.e. we are converting to OnceAction). + explicit Impl(R&& input_value) + : state_(new State(std::move(input_value))) {} + + // The constructor used when the return value is not allowed to move from + // the input value (i.e. we are converting to Action). + explicit Impl(const R& input_value) : state_(new State(input_value)) {} + + U operator()() && { return std::move(state_->value); } + U operator()() const& { return state_->value; } + + private: + // We put our state on the heap so that the compiler-generated copy/move + // constructors work correctly even when U is a reference-like type. This is + // necessary only because we eagerly create State::value (see the note on + // that symbol for details). If we instead had only the input value as a + // member then the default constructors would work fine. + // + // For example, when R is std::string and U is std::string_view, value is a + // reference to the string backed by input_value. The copy constructor would + // copy both, so that we wind up with a new input_value object (with the + // same contents) and a reference to the *old* input_value object rather + // than the new one. + struct State { + explicit State(const R& input_value_in) + : input_value(input_value_in), + // Make an implicit conversion to Result before initializing the U + // object we store, avoiding calling any explicit constructor of U + // from R. + // + // This simulates the language rules: a function with return type U + // that does `return R()` requires R to be implicitly convertible to + // U, and uses that path for the conversion, even U Result has an + // explicit constructor from R. + value(ImplicitCast_(internal::as_const(input_value))) {} + + // As above, but for the case where we're moving from the ReturnAction + // object because it's being used as a OnceAction. + explicit State(R&& input_value_in) + : input_value(std::move(input_value_in)), + // For the same reason as above we make an implicit conversion to U + // before initializing the value. + // + // Unlike above we provide the input value as an rvalue to the + // implicit conversion because this is a OnceAction: it's fine if it + // wants to consume the input value. + value(ImplicitCast_(std::move(input_value))) {} + + // A copy of the value originally provided by the user. We retain this in + // addition to the value of the mock function's result type below in case + // the latter is a reference-like type. See the std::string_view example + // in the documentation on Return. + R input_value; + + // The value we actually return, as the type returned by the mock function + // itself. + // + // We eagerly initialize this here, rather than lazily doing the implicit + // conversion automatically each time Perform is called, for historical + // reasons: in 2009-11, commit a070cbd91c (Google changelist 13540126) + // made the Action conversion operator eagerly convert the R value to + // U, but without keeping the R alive. This broke the use case discussed + // in the documentation for Return, making reference-like types such as + // std::string_view not safe to use as U where the input type R is a + // value-like type such as std::string. + // + // The example the commit gave was not very clear, nor was the issue + // thread (https://github.com/google/googlemock/issues/86), but it seems + // the worry was about reference-like input types R that flatten to a + // value-like type U when being implicitly converted. An example of this + // is std::vector::reference, which is often a proxy type with an + // reference to the underlying vector: + // + // // Helper method: have the mock function return bools according + // // to the supplied script. + // void SetActions(MockFunction& mock, + // const std::vector& script) { + // for (size_t i = 0; i < script.size(); ++i) { + // EXPECT_CALL(mock, Call(i)).WillOnce(Return(script[i])); + // } + // } + // + // TEST(Foo, Bar) { + // // Set actions using a temporary vector, whose operator[] + // // returns proxy objects that references that will be + // // dangling once the call to SetActions finishes and the + // // vector is destroyed. + // MockFunction mock; + // SetActions(mock, {false, true}); + // + // EXPECT_FALSE(mock.AsStdFunction()(0)); + // EXPECT_TRUE(mock.AsStdFunction()(1)); + // } + // + // This eager conversion helps with a simple case like this, but doesn't + // fully make these types work in general. For example the following still + // uses a dangling reference: + // + // TEST(Foo, Baz) { + // MockFunction()> mock; + // + // // Return the same vector twice, and then the empty vector + // // thereafter. + // auto action = Return(std::initializer_list{ + // "taco", "burrito", + // }); + // + // EXPECT_CALL(mock, Call) + // .WillOnce(action) + // .WillOnce(action) + // .WillRepeatedly(Return(std::vector{})); + // + // EXPECT_THAT(mock.AsStdFunction()(), + // ElementsAre("taco", "burrito")); + // EXPECT_THAT(mock.AsStdFunction()(), + // ElementsAre("taco", "burrito")); + // EXPECT_THAT(mock.AsStdFunction()(), IsEmpty()); + // } + // + U value; + }; + + const std::shared_ptr state_; + }; + + R value_; +}; + +// A specialization of ReturnAction when R is ByMoveWrapper for some T. +// +// This version applies the type system-defeating hack of moving from T even in +// the const call operator, checking at runtime that it isn't called more than +// once, since the user has declared their intent to do so by using ByMove. +template +class ReturnAction> final { + public: + explicit ReturnAction(ByMoveWrapper wrapper) + : state_(new State(std::move(wrapper.payload))) {} + + T operator()() const { + GTEST_CHECK_(!state_->called) + << "A ByMove() action must be performed at most once."; + + state_->called = true; + return std::move(state_->value); + } + + private: + // We store our state on the heap so that we are copyable as required by + // Action, despite the fact that we are stateful and T may not be copyable. + struct State { + explicit State(T&& value_in) : value(std::move(value_in)) {} + + T value; + bool called = false; + }; + + const std::shared_ptr state_; +}; + +// Implements the ReturnNull() action. +class ReturnNullAction { + public: + // Allows ReturnNull() to be used in any pointer-returning function. In C++11 + // this is enforced by returning nullptr, and in non-C++11 by asserting a + // pointer type on compile time. + template + static Result Perform(const ArgumentTuple&) { + return nullptr; + } +}; + +// Implements the Return() action. +class ReturnVoidAction { + public: + // Allows Return() to be used in any void-returning function. + template + static void Perform(const ArgumentTuple&) { + static_assert(std::is_void::value, "Result should be void."); + } +}; + +// Implements the polymorphic ReturnRef(x) action, which can be used +// in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefAction { + public: + // Constructs a ReturnRefAction object from the reference to be returned. + explicit ReturnRefAction(T& ref) : ref_(ref) {} // NOLINT + + // This template type conversion operator allows ReturnRef(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRef(x) when Return(x) + // should be used, and generates some helpful error message. + static_assert(std::is_reference::value, + "use Return instead of ReturnRef to return a value"); + return Action(new Impl(ref_)); + } + + private: + // Implements the ReturnRef(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(T& ref) : ref_(ref) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return ref_; } + + private: + T& ref_; + }; + + T& ref_; +}; + +// Implements the polymorphic ReturnRefOfCopy(x) action, which can be +// used in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefOfCopyAction { + public: + // Constructs a ReturnRefOfCopyAction object from the reference to + // be returned. + explicit ReturnRefOfCopyAction(const T& value) : value_(value) {} // NOLINT + + // This template type conversion operator allows ReturnRefOfCopy(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRefOfCopy(x) when Return(x) + // should be used, and generates some helpful error message. + static_assert(std::is_reference::value, + "use Return instead of ReturnRefOfCopy to return a value"); + return Action(new Impl(value_)); + } + + private: + // Implements the ReturnRefOfCopy(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const T& value) : value_(value) {} // NOLINT + + Result Perform(const ArgumentTuple&) override { return value_; } + + private: + T value_; + }; + + const T value_; +}; + +// Implements the polymorphic ReturnRoundRobin(v) action, which can be +// used in any function that returns the element_type of v. +template +class ReturnRoundRobinAction { + public: + explicit ReturnRoundRobinAction(std::vector values) { + GTEST_CHECK_(!values.empty()) + << "ReturnRoundRobin requires at least one element."; + state_->values = std::move(values); + } + + template + T operator()(Args&&...) const { + return state_->Next(); + } + + private: + struct State { + T Next() { + T ret_val = values[i++]; + if (i == values.size()) i = 0; + return ret_val; + } + + std::vector values; + size_t i = 0; + }; + std::shared_ptr state_ = std::make_shared(); +}; + +// Implements the polymorphic DoDefault() action. +class DoDefaultAction { + public: + // This template type conversion operator allows DoDefault() to be + // used in any function. + template + operator Action() const { + return Action(); + } // NOLINT +}; + +// Implements the Assign action to set a given pointer referent to a +// particular value. +template +class AssignAction { + public: + AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {} + + template + void Perform(const ArgumentTuple& /* args */) const { + *ptr_ = value_; + } + + private: + T1* const ptr_; + const T2 value_; +}; + +#ifndef GTEST_OS_WINDOWS_MOBILE + +// Implements the SetErrnoAndReturn action to simulate return from +// various system calls and libc functions. +template +class SetErrnoAndReturnAction { + public: + SetErrnoAndReturnAction(int errno_value, T result) + : errno_(errno_value), result_(result) {} + template + Result Perform(const ArgumentTuple& /* args */) const { + errno = errno_; + return result_; + } + + private: + const int errno_; + const T result_; +}; + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetArgumentPointee(x) action for any function +// whose N-th argument (0-based) is a pointer to x's type. +template +struct SetArgumentPointeeAction { + A value; + + template + void operator()(const Args&... args) const { + *::std::get(std::tie(args...)) = value; + } +}; + +// Implements the Invoke(object_ptr, &Class::Method) action. +template +struct InvokeMethodAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + template + auto operator()(Args&&... args) const + -> decltype((obj_ptr->*method_ptr)(std::forward(args)...)) { + return (obj_ptr->*method_ptr)(std::forward(args)...); + } +}; + +// Implements the InvokeWithoutArgs(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. InvokeWithoutArgs(f) can be used as an +// Action as long as f's type is compatible with F. +template +struct InvokeWithoutArgsAction { + FunctionImpl function_impl; + + // Allows InvokeWithoutArgs(f) to be used as any action whose type is + // compatible with f. + template + auto operator()(const Args&...) -> decltype(function_impl()) { + return function_impl(); + } +}; + +// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action. +template +struct InvokeMethodWithoutArgsAction { + Class* const obj_ptr; + const MethodPtr method_ptr; + + using ReturnType = + decltype((std::declval()->*std::declval())()); + + template + ReturnType operator()(const Args&...) const { + return (obj_ptr->*method_ptr)(); + } +}; + +// Implements the IgnoreResult(action) action. +template +class IgnoreResultAction { + public: + explicit IgnoreResultAction(const A& action) : action_(action) {} + + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename internal::Function::Result Result; + + // Asserts at compile time that F returns void. + static_assert(std::is_void::value, "Result type should be void."); + + return Action(new Impl(action_)); + } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const A& action) : action_(action) {} + + void Perform(const ArgumentTuple& args) override { + // Performs the action and ignores its result. + action_.Perform(args); + } + + private: + // Type OriginalFunction is the same as F except that its return + // type is IgnoredValue. + typedef + typename internal::Function::MakeResultIgnoredValue OriginalFunction; + + const Action action_; + }; + + const A action_; +}; + +template +struct WithArgsAction { + InnerAction inner_action; + + // The signature of the function as seen by the inner action, given an out + // action with the given result and argument types. + template + using InnerSignature = + R(typename std::tuple_element>::type...); + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>...)>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + struct OA { + OnceAction> inner_action; + + R operator()(Args&&... args) && { + return std::move(inner_action) + .Call(std::get( + std::forward_as_tuple(std::forward(args)...))...); + } + }; + + return OA{std::move(inner_action)}; + } + + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>...)>>::value, + int>::type = 0> + operator Action() const { // NOLINT + Action> converted(inner_action); + + return [converted](Args&&... args) -> R { + return converted.Perform(std::forward_as_tuple( + std::get(std::forward_as_tuple(std::forward(args)...))...)); + }; + } +}; + +template +class DoAllAction; + +// Base case: only a single action. +template +class DoAllAction { + public: + struct UserConstructorTag {}; + + template + explicit DoAllAction(UserConstructorTag, T&& action) + : final_action_(std::forward(action)) {} + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + template >::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return std::move(final_action_); + } + + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>::value, + int>::type = 0> + operator Action() const { // NOLINT + return final_action_; + } + + private: + FinalAction final_action_; +}; + +// Recursive case: support N actions by calling the initial action and then +// calling through to the base class containing N-1 actions. +template +class DoAllAction + : private DoAllAction { + private: + using Base = DoAllAction; + + // The type of reference that should be provided to an initial action for a + // mocked function parameter of type T. + // + // There are two quirks here: + // + // * Unlike most forwarding functions, we pass scalars through by value. + // This isn't strictly necessary because an lvalue reference would work + // fine too and be consistent with other non-reference types, but it's + // perhaps less surprising. + // + // For example if the mocked function has signature void(int), then it + // might seem surprising for the user's initial action to need to be + // convertible to Action. This is perhaps less + // surprising for a non-scalar type where there may be a performance + // impact, or it might even be impossible, to pass by value. + // + // * More surprisingly, `const T&` is often not a const reference type. + // By the reference collapsing rules in C++17 [dcl.ref]/6, if T refers to + // U& or U&& for some non-scalar type U, then InitialActionArgType is + // U&. In other words, we may hand over a non-const reference. + // + // So for example, given some non-scalar type Obj we have the following + // mappings: + // + // T InitialActionArgType + // ------- ----------------------- + // Obj const Obj& + // Obj& Obj& + // Obj&& Obj& + // const Obj const Obj& + // const Obj& const Obj& + // const Obj&& const Obj& + // + // In other words, the initial actions get a mutable view of an non-scalar + // argument if and only if the mock function itself accepts a non-const + // reference type. They are never given an rvalue reference to an + // non-scalar type. + // + // This situation makes sense if you imagine use with a matcher that is + // designed to write through a reference. For example, if the caller wants + // to fill in a reference argument and then return a canned value: + // + // EXPECT_CALL(mock, Call) + // .WillOnce(DoAll(SetArgReferee<0>(17), Return(19))); + // + template + using InitialActionArgType = + typename std::conditional::value, T, const T&>::type; + + public: + struct UserConstructorTag {}; + + template + explicit DoAllAction(UserConstructorTag, T&& initial_action, + U&&... other_actions) + : Base({}, std::forward(other_actions)...), + initial_action_(std::forward(initial_action)) {} + + template ...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + OnceAction...)> initial_action; + OnceAction remaining_actions; + + R operator()(Args... args) && { + std::move(initial_action) + .Call(static_cast>(args)...); + + return std::move(remaining_actions).Call(std::forward(args)...); + } + }; + + return OA{ + std::move(initial_action_), + std::move(static_cast(*this)), + }; + } + + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + // Both the initial action and the rest must support conversion to + // Action. + std::is_convertible...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator Action() const { // NOLINT + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + Action...)> initial_action; + Action remaining_actions; + + R operator()(Args... args) const { + initial_action.Perform(std::forward_as_tuple( + static_cast>(args)...)); + + return remaining_actions.Perform( + std::forward_as_tuple(std::forward(args)...)); + } + }; + + return OA{ + initial_action_, + static_cast(*this), + }; + } + + private: + InitialAction initial_action_; +}; + +template +struct ReturnNewAction { + T* operator()() const { + return internal::Apply( + [](const Params&... unpacked_params) { + return new T(unpacked_params...); + }, + params); + } + std::tuple params; +}; + +template +struct ReturnArgAction { + template ::type> + auto operator()(Args&&... args) const -> decltype(std::get( + std::forward_as_tuple(std::forward(args)...))) { + return std::get(std::forward_as_tuple(std::forward(args)...)); + } +}; + +template +struct SaveArgAction { + Ptr pointer; + + template + void operator()(const Args&... args) const { + *pointer = std::get(std::tie(args...)); + } +}; + +template +struct SaveArgPointeeAction { + Ptr pointer; + + template + void operator()(const Args&... args) const { + *pointer = *std::get(std::tie(args...)); + } +}; + +template +struct SetArgRefereeAction { + T value; + + template + void operator()(Args&&... args) const { + using argk_type = + typename ::std::tuple_element>::type; + static_assert(std::is_lvalue_reference::value, + "Argument must be a reference type."); + std::get(std::tie(args...)) = value; + } +}; + +template +struct SetArrayArgumentAction { + I1 first; + I2 last; + + template + void operator()(const Args&... args) const { + auto value = std::get(std::tie(args...)); + for (auto it = first; it != last; ++it, (void)++value) { + *value = *it; + } + } +}; + +template +struct DeleteArgAction { + template + void operator()(const Args&... args) const { + delete std::get(std::tie(args...)); + } +}; + +template +struct ReturnPointeeAction { + Ptr pointer; + template + auto operator()(const Args&...) const -> decltype(*pointer) { + return *pointer; + } +}; + +#if GTEST_HAS_EXCEPTIONS +template +struct ThrowAction { + T exception; + // We use a conversion operator to adapt to any return type. + template + operator Action() const { // NOLINT + T copy = exception; + return [copy](Args...) -> R { throw copy; }; + } +}; +#endif // GTEST_HAS_EXCEPTIONS + +} // namespace internal + +// An Unused object can be implicitly constructed from ANY value. +// This is handy when defining actions that ignore some or all of the +// mock function arguments. For example, given +// +// MOCK_METHOD3(Foo, double(const string& label, double x, double y)); +// MOCK_METHOD3(Bar, double(int index, double x, double y)); +// +// instead of +// +// double DistanceToOriginWithLabel(const string& label, double x, double y) { +// return sqrt(x*x + y*y); +// } +// double DistanceToOriginWithIndex(int index, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)) +// .WillOnce(Invoke(DistanceToOriginWithLabel)); +// EXPECT_CALL(mock, Bar(5, _, _)) +// .WillOnce(Invoke(DistanceToOriginWithIndex)); +// +// you could write +// +// // We can declare any uninteresting argument as Unused. +// double DistanceToOrigin(Unused, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXPECT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin)); +// EXPECT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin)); +typedef internal::IgnoredValue Unused; + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. All but the last action will have a readonly view of the +// arguments. +template +internal::DoAllAction::type...> DoAll( + Action&&... action) { + return internal::DoAllAction::type...>( + {}, std::forward(action)...); +} + +// WithArg(an_action) creates an action that passes the k-th +// (0-based) argument of the mock function to an_action and performs +// it. It adapts an action accepting one argument to one that accepts +// multiple arguments. For convenience, we also provide +// WithArgs(an_action) (defined below) as a synonym. +template +internal::WithArgsAction::type, k> WithArg( + InnerAction&& action) { + return {std::forward(action)}; +} + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. +template +internal::WithArgsAction::type, k, ks...> +WithArgs(InnerAction&& action) { + return {std::forward(action)}; +} + +// WithoutArgs(inner_action) can be used in a mock function with a +// non-empty argument list to perform inner_action, which takes no +// argument. In other words, it adapts an action accepting no +// argument to one that accepts (and ignores) arguments. +template +internal::WithArgsAction::type> WithoutArgs( + InnerAction&& action) { + return {std::forward(action)}; +} + +// Creates an action that returns a value. +// +// The returned type can be used with a mock function returning a non-void, +// non-reference type U as follows: +// +// * If R is convertible to U and U is move-constructible, then the action can +// be used with WillOnce. +// +// * If const R& is convertible to U and U is copy-constructible, then the +// action can be used with both WillOnce and WillRepeatedly. +// +// The mock expectation contains the R value from which the U return value is +// constructed (a move/copy of the argument to Return). This means that the R +// value will survive at least until the mock object's expectations are cleared +// or the mock object is destroyed, meaning that U can safely be a +// reference-like type such as std::string_view: +// +// // The mock function returns a view of a copy of the string fed to +// // Return. The view is valid even after the action is performed. +// MockFunction mock; +// EXPECT_CALL(mock, Call).WillOnce(Return(std::string("taco"))); +// const std::string_view result = mock.AsStdFunction()(); +// EXPECT_EQ("taco", result); +// +template +internal::ReturnAction Return(R value) { + return internal::ReturnAction(std::move(value)); +} + +// Creates an action that returns NULL. +inline PolymorphicAction ReturnNull() { + return MakePolymorphicAction(internal::ReturnNullAction()); +} + +// Creates an action that returns from a void function. +inline PolymorphicAction Return() { + return MakePolymorphicAction(internal::ReturnVoidAction()); +} + +// Creates an action that returns the reference to a variable. +template +inline internal::ReturnRefAction ReturnRef(R& x) { // NOLINT + return internal::ReturnRefAction(x); +} + +// Prevent using ReturnRef on reference to temporary. +template +internal::ReturnRefAction ReturnRef(R&&) = delete; + +// Creates an action that returns the reference to a copy of the +// argument. The copy is created when the action is constructed and +// lives as long as the action. +template +inline internal::ReturnRefOfCopyAction ReturnRefOfCopy(const R& x) { + return internal::ReturnRefOfCopyAction(x); +} + +// DEPRECATED: use Return(x) directly with WillOnce. +// +// Modifies the parent action (a Return() action) to perform a move of the +// argument instead of a copy. +// Return(ByMove()) actions can only be executed once and will assert this +// invariant. +template +internal::ByMoveWrapper ByMove(R x) { + return internal::ByMoveWrapper(std::move(x)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template +internal::ReturnRoundRobinAction ReturnRoundRobin(std::vector vals) { + return internal::ReturnRoundRobinAction(std::move(vals)); +} + +// Creates an action that returns an element of `vals`. Calling this action will +// repeatedly return the next value from `vals` until it reaches the end and +// will restart from the beginning. +template +internal::ReturnRoundRobinAction ReturnRoundRobin( + std::initializer_list vals) { + return internal::ReturnRoundRobinAction(std::vector(vals)); +} + +// Creates an action that does the default action for the give mock function. +inline internal::DoDefaultAction DoDefault() { + return internal::DoDefaultAction(); +} + +// Creates an action that sets the variable pointed by the N-th +// (0-based) function argument to 'value'. +template +internal::SetArgumentPointeeAction SetArgPointee(T value) { + return {std::move(value)}; +} + +// The following version is DEPRECATED. +template +internal::SetArgumentPointeeAction SetArgumentPointee(T value) { + return {std::move(value)}; +} + +// Creates an action that sets a pointer referent to a given value. +template +PolymorphicAction> Assign(T1* ptr, T2 val) { + return MakePolymorphicAction(internal::AssignAction(ptr, val)); +} + +#ifndef GTEST_OS_WINDOWS_MOBILE + +// Creates an action that sets errno and returns the appropriate error. +template +PolymorphicAction> SetErrnoAndReturn( + int errval, T result) { + return MakePolymorphicAction( + internal::SetErrnoAndReturnAction(errval, result)); +} + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Various overloads for Invoke(). + +// Legacy function. +// Actions can now be implicitly constructed from callables. No need to create +// wrapper objects. +// This function exists for backwards compatibility. +template +typename std::decay::type Invoke(FunctionImpl&& function_impl) { + return std::forward(function_impl); +} + +// Creates an action that invokes the given method on the given object +// with the mock function's arguments. +template +internal::InvokeMethodAction Invoke(Class* obj_ptr, + MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that invokes 'function_impl' with no argument. +template +internal::InvokeWithoutArgsAction::type> +InvokeWithoutArgs(FunctionImpl function_impl) { + return {std::move(function_impl)}; +} + +// Creates an action that invokes the given method on the given object +// with no argument. +template +internal::InvokeMethodWithoutArgsAction InvokeWithoutArgs( + Class* obj_ptr, MethodPtr method_ptr) { + return {obj_ptr, method_ptr}; +} + +// Creates an action that performs an_action and throws away its +// result. In other words, it changes the return type of an_action to +// void. an_action MUST NOT return void, or the code won't compile. +template +inline internal::IgnoreResultAction IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef(derived) +// +// N.B. ByRef is redundant with std::ref, std::cref and std::reference_wrapper. +// However, it may still be used for consistency with ByMove(). +template +inline ::std::reference_wrapper ByRef(T& l_value) { // NOLINT + return ::std::reference_wrapper(l_value); +} + +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +template +internal::ReturnNewAction::type...> ReturnNew( + Params&&... params) { + return {std::forward_as_tuple(std::forward(params)...)}; +} + +// Action ReturnArg() returns the k-th argument of the mock function. +template +internal::ReturnArgAction ReturnArg() { + return {}; +} + +// Action SaveArg(pointer) saves the k-th (0-based) argument of the +// mock function to *pointer. +template +internal::SaveArgAction SaveArg(Ptr pointer) { + return {pointer}; +} + +// Action SaveArgPointee(pointer) saves the value pointed to +// by the k-th (0-based) argument of the mock function to *pointer. +template +internal::SaveArgPointeeAction SaveArgPointee(Ptr pointer) { + return {pointer}; +} + +// Action SetArgReferee(value) assigns 'value' to the variable +// referenced by the k-th (0-based) argument of the mock function. +template +internal::SetArgRefereeAction::type> SetArgReferee( + T&& value) { + return {std::forward(value)}; +} + +// Action SetArrayArgument(first, last) copies the elements in +// source range [first, last) to the array pointed to by the k-th +// (0-based) argument, which can be either a pointer or an +// iterator. The action does not take ownership of the elements in the +// source range. +template +internal::SetArrayArgumentAction SetArrayArgument(I1 first, + I2 last) { + return {first, last}; +} + +// Action DeleteArg() deletes the k-th (0-based) argument of the mock +// function. +template +internal::DeleteArgAction DeleteArg() { + return {}; +} + +// This action returns the value pointed to by 'pointer'. +template +internal::ReturnPointeeAction ReturnPointee(Ptr pointer) { + return {pointer}; +} + +// Action Throw(exception) can be used in a mock function of any type +// to throw the given exception. Any copyable value can be thrown. +#if GTEST_HAS_EXCEPTIONS +template +internal::ThrowAction::type> Throw(T&& exception) { + return {std::forward(exception)}; +} +#endif // GTEST_HAS_EXCEPTIONS + +namespace internal { + +// A macro from the ACTION* family (defined later in gmock-generated-actions.h) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// Builds an implementation of an Action<> for some particular signature, using +// a class defined by an ACTION* macro. +template +struct ActionImpl; + +template +struct ImplBase { + struct Holder { + // Allows each copy of the Action<> to get to the Impl. + explicit operator const Impl&() const { return *ptr; } + std::shared_ptr ptr; + }; + using type = typename std::conditional::value, + Impl, Holder>::type; +}; + +template +struct ActionImpl : ImplBase::type { + using Base = typename ImplBase::type; + using function_type = R(Args...); + using args_type = std::tuple; + + ActionImpl() = default; // Only defined if appropriate for Base. + explicit ActionImpl(std::shared_ptr impl) : Base{std::move(impl)} {} + + R operator()(Args&&... arg) const { + static constexpr size_t kMaxArgs = + sizeof...(Args) <= 10 ? sizeof...(Args) : 10; + return Apply(MakeIndexSequence{}, + MakeIndexSequence<10 - kMaxArgs>{}, + args_type{std::forward(arg)...}); + } + + template + R Apply(IndexSequence, IndexSequence, + const args_type& args) const { + // Impl need not be specific to the signature of action being implemented; + // only the implementing function body needs to have all of the specific + // types instantiated. Up to 10 of the args that are provided by the + // args_type get passed, followed by a dummy of unspecified type for the + // remainder up to 10 explicit args. + static constexpr ExcessiveArg kExcessArg{}; + return static_cast(*this) + .template gmock_PerformImpl< + /*function_type=*/function_type, /*return_type=*/R, + /*args_type=*/args_type, + /*argN_type=*/ + typename std::tuple_element::type...>( + /*args=*/args, std::get(args)..., + ((void)excess_id, kExcessArg)...); + } +}; + +// Stores a default-constructed Impl as part of the Action<>'s +// std::function<>. The Impl should be trivial to copy. +template +::testing::Action MakeAction() { + return ::testing::Action(ActionImpl()); +} + +// Stores just the one given instance of Impl. +template +::testing::Action MakeAction(std::shared_ptr impl) { + return ::testing::Action(ActionImpl(std::move(impl))); +} + +#define GMOCK_INTERNAL_ARG_UNUSED(i, data, el) \ + , const arg##i##_type& arg##i GTEST_ATTRIBUTE_UNUSED_ +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_ \ + const args_type& args GTEST_ATTRIBUTE_UNUSED_ GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_ARG_UNUSED, , 10) + +#define GMOCK_INTERNAL_ARG(i, data, el) , const arg##i##_type& arg##i +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_ \ + const args_type& args GMOCK_PP_REPEAT(GMOCK_INTERNAL_ARG, , 10) + +#define GMOCK_INTERNAL_TEMPLATE_ARG(i, data, el) , typename arg##i##_type +#define GMOCK_ACTION_TEMPLATE_ARGS_NAMES_ \ + GMOCK_PP_TAIL(GMOCK_PP_REPEAT(GMOCK_INTERNAL_TEMPLATE_ARG, , 10)) + +#define GMOCK_INTERNAL_TYPENAME_PARAM(i, data, param) , typename param##_type +#define GMOCK_ACTION_TYPENAME_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPENAME_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_PARAM(i, data, param) , param##_type +#define GMOCK_ACTION_TYPE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_PARAM, , params)) + +#define GMOCK_INTERNAL_TYPE_GVALUE_PARAM(i, data, param) \ + , param##_type gmock_p##i +#define GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_GVALUE_PARAM(i, data, param) \ + , std::forward(gmock_p##i) +#define GMOCK_ACTION_GVALUE_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GVALUE_PARAM, , params)) + +#define GMOCK_INTERNAL_INIT_PARAM(i, data, param) \ + , param(::std::forward(gmock_p##i)) +#define GMOCK_ACTION_INIT_PARAMS_(params) \ + GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_INIT_PARAM, , params)) + +#define GMOCK_INTERNAL_FIELD_PARAM(i, data, param) param##_type param; +#define GMOCK_ACTION_FIELD_PARAMS_(params) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_FIELD_PARAM, , params) + +#define GMOCK_INTERNAL_ACTION(name, full_name, params) \ + template \ + class full_name { \ + public: \ + explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : impl_(std::make_shared( \ + GMOCK_ACTION_GVALUE_PARAMS_(params))) {} \ + full_name(const full_name&) = default; \ + full_name(full_name&&) noexcept = default; \ + template \ + operator ::testing::Action() const { \ + return ::testing::internal::MakeAction(impl_); \ + } \ + \ + private: \ + class gmock_Impl { \ + public: \ + explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : GMOCK_ACTION_INIT_PARAMS_(params) {} \ + template \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + GMOCK_ACTION_FIELD_PARAMS_(params) \ + }; \ + std::shared_ptr impl_; \ + }; \ + template \ + inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) GTEST_MUST_USE_RESULT_; \ + template \ + inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) { \ + return full_name( \ + GMOCK_ACTION_GVALUE_PARAMS_(params)); \ + } \ + template \ + template \ + return_type \ + full_name::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +} // namespace internal + +// Similar to GMOCK_INTERNAL_ACTION, but no bound parameters are stored. +#define ACTION(name) \ + class name##Action { \ + public: \ + explicit name##Action() noexcept {} \ + name##Action(const name##Action&) noexcept {} \ + template \ + operator ::testing::Action() const { \ + return ::testing::internal::MakeAction(); \ + } \ + \ + private: \ + class gmock_Impl { \ + public: \ + template \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + }; \ + }; \ + inline name##Action name() GTEST_MUST_USE_RESULT_; \ + inline name##Action name() { return name##Action(); } \ + template \ + return_type name##Action::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP, (__VA_ARGS__)) + +#define ACTION_P2(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP2, (__VA_ARGS__)) + +#define ACTION_P3(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP3, (__VA_ARGS__)) + +#define ACTION_P4(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP4, (__VA_ARGS__)) + +#define ACTION_P5(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP5, (__VA_ARGS__)) + +#define ACTION_P6(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP6, (__VA_ARGS__)) + +#define ACTION_P7(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP7, (__VA_ARGS__)) + +#define ACTION_P8(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP8, (__VA_ARGS__)) + +#define ACTION_P9(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP9, (__VA_ARGS__)) + +#define ACTION_P10(name, ...) \ + GMOCK_INTERNAL_ACTION(name, name##ActionP10, (__VA_ARGS__)) + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100 + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-cardinalities.h b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-cardinalities.h new file mode 100644 index 000000000..533e604f3 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-cardinalities.h @@ -0,0 +1,159 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include + +#include +#include // NOLINT + +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() = default; + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true if and only if call_count calls will saturate this + // cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a std::shared_ptr +// to const CardinalityInterface. Don't inherit from Cardinality! +class GTEST_API_ Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() = default; + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true if and only if call_count calls will satisfy this + // cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will saturate this + // cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true if and only if call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + + private: + std::shared_ptr impl_; +}; + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-function-mocker.h b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-function-mocker.h new file mode 100644 index 000000000..1a1f126e4 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-function-mocker.h @@ -0,0 +1,518 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements MOCK_METHOD. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ + +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-pp.h" + +namespace testing { +namespace internal { +template +using identity_t = T; + +template +struct ThisRefAdjuster { + template + using AdjustT = typename std::conditional< + std::is_const::type>::value, + typename std::conditional::value, + const T&, const T&&>::type, + typename std::conditional::value, T&, + T&&>::type>::type; + + template + static AdjustT Adjust(const MockType& mock) { + return static_cast>(const_cast(mock)); + } +}; + +constexpr bool PrefixOf(const char* a, const char* b) { + return *a == 0 || (*a == *b && internal::PrefixOf(a + 1, b + 1)); +} + +template +constexpr bool StartsWith(const char (&prefix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(prefix, str); +} + +template +constexpr bool EndsWith(const char (&suffix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(suffix, str + M - N); +} + +template +constexpr bool Equals(const char (&a)[N], const char (&b)[M]) { + return N == M && internal::PrefixOf(a, b); +} + +template +constexpr bool ValidateSpec(const char (&spec)[N]) { + return internal::Equals("const", spec) || + internal::Equals("override", spec) || + internal::Equals("final", spec) || + internal::Equals("noexcept", spec) || + (internal::StartsWith("noexcept(", spec) && + internal::EndsWith(")", spec)) || + internal::Equals("ref(&)", spec) || + internal::Equals("ref(&&)", spec) || + (internal::StartsWith("Calltype(", spec) && + internal::EndsWith(")", spec)); +} + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; +} // namespace testing + +#define MOCK_METHOD(...) \ + GMOCK_INTERNAL_WARNING_PUSH() \ + GMOCK_INTERNAL_WARNING_CLANG(ignored, "-Wunused-member-function") \ + GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__) \ + GMOCK_INTERNAL_WARNING_POP() + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_1(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_2(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \ + GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ()) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \ + GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \ + GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \ + GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \ + (GMOCK_INTERNAL_SIGNATURE(_Ret, _Args))) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_6(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_7(...) \ + GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__) + +#define GMOCK_INTERNAL_WRONG_ARITY(...) \ + static_assert( \ + false, \ + "MOCK_METHOD must be called with 3 or 4 arguments. _Ret, " \ + "_MethodName, _Args and optionally _Spec. _Args and _Spec must be " \ + "enclosed in parentheses. If _Ret is a type with unprotected commas, " \ + "it must also be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Tuple) \ + static_assert( \ + GMOCK_PP_IS_ENCLOSED_PARENS(_Tuple), \ + GMOCK_PP_STRINGIZE(_Tuple) " should be enclosed in parentheses.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(_N, ...) \ + static_assert( \ + std::is_function<__VA_ARGS__>::value, \ + "Signature must be a function type, maybe return type contains " \ + "unprotected comma."); \ + static_assert( \ + ::testing::tuple_size::ArgumentTuple>::value == _N, \ + "This method does not take " GMOCK_PP_STRINGIZE( \ + _N) " arguments. Parenthesize all types with unprotected commas.") + +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT, ~, _Spec) + +#define GMOCK_INTERNAL_MOCK_METHOD_IMPL(_N, _MethodName, _Constness, \ + _Override, _Final, _NoexceptSpec, \ + _CallType, _RefSpec, _Signature) \ + typename ::testing::internal::Function::Result \ + GMOCK_INTERNAL_EXPAND(_CallType) \ + _MethodName(GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) \ + _RefSpec _NoexceptSpec GMOCK_PP_IF(_Override, override, ) \ + GMOCK_PP_IF(_Final, final, ) { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .SetOwnerAndName(this, #_MethodName); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .Invoke(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, _Signature, _N)); \ + } \ + ::testing::MockSpec gmock_##_MethodName( \ + GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_PARAMETER, _Signature, _N)) \ + GMOCK_PP_IF(_Constness, const, ) _RefSpec { \ + GMOCK_MOCKER_(_N, _Constness, _MethodName).RegisterOwner(this); \ + return GMOCK_MOCKER_(_N, _Constness, _MethodName) \ + .With(GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_ARGUMENT, , _N)); \ + } \ + ::testing::MockSpec gmock_##_MethodName( \ + const ::testing::internal::WithoutMatchers&, \ + GMOCK_PP_IF(_Constness, const, )::testing::internal::Function< \ + GMOCK_PP_REMOVE_PARENS(_Signature)>*) const _RefSpec _NoexceptSpec { \ + return ::testing::internal::ThisRefAdjuster::Adjust(*this) \ + .gmock_##_MethodName(GMOCK_PP_REPEAT( \ + GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N)); \ + } \ + mutable ::testing::FunctionMocker \ + GMOCK_MOCKER_(_N, _Constness, _MethodName) + +#define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__ + +// Valid modifiers. +#define GMOCK_INTERNAL_HAS_CONST(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_OVERRIDE(_Tuple) \ + GMOCK_PP_HAS_COMMA( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_OVERRIDE, ~, _Tuple)) + +#define GMOCK_INTERNAL_HAS_FINAL(_Tuple) \ + GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_FINAL, ~, _Tuple)) + +#define GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT, ~, _Tuple) + +#define GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \ + _elem, ) + +#define GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE, ~, _Tuple) + +#define GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + +#define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple) + +#define GMOCK_INTERNAL_REF_SPEC_IF_REF(_i, _, _elem) \ + GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + +#ifdef GMOCK_INTERNAL_STRICT_SPEC_ASSERT +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + ::testing::internal::ValidateSpec(GMOCK_PP_STRINGIZE(_elem)), \ + "Token \'" GMOCK_PP_STRINGIZE( \ + _elem) "\' cannot be recognized as a valid specification " \ + "modifier. Is a ',' missing?"); +#else +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem))) == 1, \ + GMOCK_PP_STRINGIZE( \ + _elem) " cannot be recognized as a valid specification modifier."); +#endif // GMOCK_INTERNAL_STRICT_SPEC_ASSERT + +// Modifiers implementation. +#define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CONST_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CONST_I_const , + +#define GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_OVERRIDE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_OVERRIDE_I_override , + +#define GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_FINAL_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_FINAL_I_final , + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_NOEXCEPT_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_NOEXCEPT_I_noexcept , + +#define GMOCK_INTERNAL_DETECT_REF(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_REF_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_REF_I_ref , + +#define GMOCK_INTERNAL_UNPACK_ref(x) x + +#define GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CALLTYPE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CALLTYPE_I_Calltype , + +#define GMOCK_INTERNAL_UNPACK_Calltype(...) __VA_ARGS__ + +// Note: The use of `identity_t` here allows _Ret to represent return types that +// would normally need to be specified in a different way. For example, a method +// returning a function pointer must be written as +// +// fn_ptr_return_t (*method(method_args_t...))(fn_ptr_args_t...) +// +// But we only support placing the return type at the beginning. To handle this, +// we wrap all calls in identity_t, so that a declaration will be expanded to +// +// identity_t method(method_args_t...) +// +// This allows us to work around the syntactic oddities of function/method +// types. +#define GMOCK_INTERNAL_SIGNATURE(_Ret, _Args) \ + ::testing::internal::identity_t( \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_TYPE, _, _Args)) + +#define GMOCK_INTERNAL_GET_TYPE(_i, _, _elem) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_elem), GMOCK_PP_REMOVE_PARENS, \ + GMOCK_PP_IDENTITY) \ + (_elem) + +#define GMOCK_INTERNAL_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_FORWARD_ARG(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::std::forward(gmock_a##_i) + +#define GMOCK_INTERNAL_MATCHER_PARAMETER(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + GMOCK_INTERNAL_MATCHER_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \ + gmock_a##_i + +#define GMOCK_INTERNAL_MATCHER_ARGUMENT(_i, _1, _2) \ + GMOCK_PP_COMMA_IF(_i) \ + gmock_a##_i + +#define GMOCK_INTERNAL_A_MATCHER_ARGUMENT(_i, _Signature, _) \ + GMOCK_PP_COMMA_IF(_i) \ + ::testing::A() + +#define GMOCK_INTERNAL_ARG_O(_i, ...) \ + typename ::testing::internal::Function<__VA_ARGS__>::template Arg<_i>::type + +#define GMOCK_INTERNAL_MATCHER_O(_i, ...) \ + const ::testing::Matcher::template Arg<_i>::type>& + +#define MOCK_METHOD0(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 0, __VA_ARGS__) +#define MOCK_METHOD1(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 1, __VA_ARGS__) +#define MOCK_METHOD2(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 2, __VA_ARGS__) +#define MOCK_METHOD3(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 3, __VA_ARGS__) +#define MOCK_METHOD4(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 4, __VA_ARGS__) +#define MOCK_METHOD5(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 5, __VA_ARGS__) +#define MOCK_METHOD6(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 6, __VA_ARGS__) +#define MOCK_METHOD7(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 7, __VA_ARGS__) +#define MOCK_METHOD8(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 8, __VA_ARGS__) +#define MOCK_METHOD9(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 9, __VA_ARGS__) +#define MOCK_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, , m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10(m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, , m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T(m, ...) MOCK_METHOD0(m, __VA_ARGS__) +#define MOCK_METHOD1_T(m, ...) MOCK_METHOD1(m, __VA_ARGS__) +#define MOCK_METHOD2_T(m, ...) MOCK_METHOD2(m, __VA_ARGS__) +#define MOCK_METHOD3_T(m, ...) MOCK_METHOD3(m, __VA_ARGS__) +#define MOCK_METHOD4_T(m, ...) MOCK_METHOD4(m, __VA_ARGS__) +#define MOCK_METHOD5_T(m, ...) MOCK_METHOD5(m, __VA_ARGS__) +#define MOCK_METHOD6_T(m, ...) MOCK_METHOD6(m, __VA_ARGS__) +#define MOCK_METHOD7_T(m, ...) MOCK_METHOD7(m, __VA_ARGS__) +#define MOCK_METHOD8_T(m, ...) MOCK_METHOD8(m, __VA_ARGS__) +#define MOCK_METHOD9_T(m, ...) MOCK_METHOD9(m, __VA_ARGS__) +#define MOCK_METHOD10_T(m, ...) MOCK_METHOD10(m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T(m, ...) MOCK_CONST_METHOD0(m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T(m, ...) MOCK_CONST_METHOD1(m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T(m, ...) MOCK_CONST_METHOD2(m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T(m, ...) MOCK_CONST_METHOD3(m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T(m, ...) MOCK_CONST_METHOD4(m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T(m, ...) MOCK_CONST_METHOD5(m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T(m, ...) MOCK_CONST_METHOD6(m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T(m, ...) MOCK_CONST_METHOD7(m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T(m, ...) MOCK_CONST_METHOD8(m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T(m, ...) MOCK_CONST_METHOD9(m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T(m, ...) MOCK_CONST_METHOD10(m, __VA_ARGS__) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 0, __VA_ARGS__) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 1, __VA_ARGS__) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 2, __VA_ARGS__) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 3, __VA_ARGS__) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 4, __VA_ARGS__) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 5, __VA_ARGS__) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 6, __VA_ARGS__) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 7, __VA_ARGS__) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 8, __VA_ARGS__) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 9, __VA_ARGS__) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 10, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 0, __VA_ARGS__) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 1, __VA_ARGS__) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 2, __VA_ARGS__) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 3, __VA_ARGS__) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 4, __VA_ARGS__) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 5, __VA_ARGS__) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 6, __VA_ARGS__) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 7, __VA_ARGS__) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 8, __VA_ARGS__) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 9, __VA_ARGS__) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 10, __VA_ARGS__) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__) + +#define GMOCK_INTERNAL_MOCK_METHODN(constness, ct, Method, args_num, ...) \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + args_num, ::testing::internal::identity_t<__VA_ARGS__>); \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + args_num, Method, GMOCK_PP_NARG0(constness), 0, 0, , ct, , \ + (::testing::internal::identity_t<__VA_ARGS__>)) + +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_ diff --git a/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-matchers.h b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-matchers.h new file mode 100644 index 000000000..0f6771377 --- /dev/null +++ b/Engine/lib/assimp/contrib/googletest/googlemock/include/gmock/gmock-matchers.h @@ -0,0 +1,5623 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// std::string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope as templates cannot be +// declared inside of a local class. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on +// https://github.com/google/googletest/blob/main/docs/gmock_cook_book.md +// +// This file also implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface interface if necessary. +// +// See googletest/include/gtest/gtest-matchers.h for the definition of class +// Matcher, class MatcherInterface, and others. + +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* + +#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gmock/internal/gmock-pp.h" +#include "gtest/gtest.h" + +// MSVC warning C5046 is new as of VS2017 version 15.8. +#if defined(_MSC_VER) && _MSC_VER >= 1915 +#define GMOCK_MAYBE_5046_ 5046 +#else +#define GMOCK_MAYBE_5046_ +#endif + +GTEST_DISABLE_MSC_WARNINGS_PUSH_( + 4251 GMOCK_MAYBE_5046_ /* class A needs to have dll-interface to be used by + clients of class B */ + /* Symbol involving type with internal linkage not defined */) + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface interface, and +// 2. a factory function that creates a Matcher object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + std::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + StringMatchResultListener(const StringMatchResultListener&) = delete; + StringMatchResultListener& operator=(const StringMatchResultListener&) = + delete; +}; + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)) or a value (for +// example, "hello"). +template +class MatcherCastImpl { + public: + static Matcher Cast(const M& polymorphic_matcher_or_value) { + // M can be a polymorphic matcher, in which case we want to use + // its conversion operator to create Matcher. Or it can be a value + // that should be passed to the Matcher's constructor. + // + // We can't call Matcher(polymorphic_matcher_or_value) when M is a + // polymorphic matcher because it'll be ambiguous if T has an implicit + // constructor from M (this usually happens when T has an implicit + // constructor from any type). + // + // It won't work to unconditionally implicit_cast + // polymorphic_matcher_or_value to Matcher because it won't trigger + // a user-defined conversion from M to T if one exists (assuming M is + // a value). + return CastImpl(polymorphic_matcher_or_value, + std::is_convertible>{}, + std::is_convertible{}); + } + + private: + template + static Matcher CastImpl(const M& polymorphic_matcher_or_value, + std::true_type /* convertible_to_matcher */, + std::integral_constant) { + // M is implicitly convertible to Matcher, which means that either + // M is a polymorphic matcher or Matcher has an implicit constructor + // from M. In both cases using the implicit conversion will produce a + // matcher. + // + // Even if T has an implicit constructor from M, it won't be called because + // creating Matcher would require a chain of two user-defined conversions + // (first to create T from M and then to create Matcher from T). + return polymorphic_matcher_or_value; + } + + // M can't be implicitly converted to Matcher, so M isn't a polymorphic + // matcher. It's a value of a type implicitly convertible to T. Use direct + // initialization to create a matcher. + static Matcher CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::true_type /* convertible_to_T */) { + return Matcher(ImplicitCast_(value)); + } + + // M can't be implicitly converted to either Matcher or T. Attempt to use + // polymorphic matcher Eq(value) in this case. + // + // Note that we first attempt to perform an implicit cast on the value and + // only fall back to the polymorphic Eq() matcher afterwards because the + // latter calls bool operator==(const Lhs& lhs, const Rhs& rhs) in the end + // which might be undefined even when Rhs is implicitly convertible to Lhs + // (e.g. std::pair vs. std::pair). + // + // We don't define this method inline as we need the declaration of Eq(). + static Matcher CastImpl(const M& value, + std::false_type /* convertible_to_matcher */, + std::false_type /* convertible_to_T */); +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template +class MatcherCastImpl> { + public: + static Matcher Cast(const Matcher& source_matcher) { + return Matcher(new Impl(source_matcher)); + } + + private: + class Impl : public MatcherInterface { + public: + explicit Impl(const Matcher& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + bool MatchAndExplain(T x, MatchResultListener* listener) const override { + using FromType = typename std::remove_cv::type>::type>::type; + using ToType = typename std::remove_cv::type>::type>::type; + // Do not allow implicitly converting base*/& to derived*/&. + static_assert( + // Do not trigger if only one of them is a pointer. That implies a + // regular conversion and not a down_cast. + (std::is_pointer::type>::value != + std::is_pointer::type>::value) || + std::is_same::value || + !std::is_base_of::value, + "Can't implicitly convert from to "); + + // Do the cast to `U` explicitly if necessary. + // Otherwise, let implicit conversions do the trick. + using CastType = + typename std::conditional::value, + T&, U>::type; + + return source_matcher_.MatchAndExplain(static_cast(x), + listener); + } + + void DescribeTo(::std::ostream* os) const override { + source_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher source_matcher_; + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template +class MatcherCastImpl> { + public: + static Matcher Cast(const Matcher& matcher) { return matcher; } +}; + +// Template specialization for parameterless Matcher. +template +class MatcherBaseImpl { + public: + MatcherBaseImpl() = default; + + template + operator ::testing::Matcher() const { // NOLINT(runtime/explicit) + return ::testing::Matcher(new + typename Derived::template gmock_Impl()); + } +}; + +// Template specialization for Matcher with parameters. +template