From 8ad9c937f171bd716bbd5692af1c501acbaded88 Mon Sep 17 00:00:00 2001 From: Krishty Date: Tue, 4 May 2021 19:10:24 +0200 Subject: [PATCH 01/44] enabled debug information in MSVC release build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No effect on runtime speed/size. Slightly slower link time, but debugging experience improves by a million times. - /Zi – Store debug information in a .pdb file, not directly in the DLL/EXE - /DEBUG:FULL – generate debug information during link - /PDBALTPATH:%_PDB% – do not store the file system path of the .pdb, just the filename and hash (no disclose paths on distribution) - /OPT:REF /OPT:ICF – remove unreferenced functions and fold identical functions (this was enabled before, but requires explicit enabling if /DEBUG:FULL is specified) --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97a3641f5..f1b60936d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,8 @@ ELSEIF(MSVC) ADD_COMPILE_OPTIONS(/wd4351) ENDIF() 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") ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) IF(NOT ASSIMP_HUNTER_ENABLED) SET(CMAKE_CXX_STANDARD 11) From 53790e82736c022b2dd935db957036e1342ea624 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 21 May 2021 12:25:36 +0100 Subject: [PATCH 02/44] Update Draco to upstream e4103dc Fixes some MSVC and mingw compiler issues Sets VERSION and SO_VERSION https://github.com/google/draco/commit/e4103dc39fe1c70c6ad40d26a01248f4b5d3887b --- contrib/draco/.ruby-version | 1 - contrib/draco/.travis.yml | 31 --------- contrib/draco/CMakeLists.txt | 8 +-- contrib/draco/README.md | 6 +- .../draco/cmake/draco_build_definitions.cmake | 9 ++- contrib/draco/cmake/draco_features.cmake | 63 ------------------- contrib/draco/cmake/draco_flags.cmake | 9 +++ contrib/draco/cmake/draco_install.cmake | 2 +- contrib/draco/cmake/draco_sanitizer.cmake | 20 +++--- contrib/draco/cmake/draco_targets.cmake | 24 ++++--- contrib/draco/src/draco/core/cycle_timer.cc | 14 ++--- contrib/draco/src/draco/core/cycle_timer.h | 7 ++- contrib/draco/src/draco/io/parser_utils.cc | 3 +- contrib/draco/src/draco/io/ply_reader.cc | 4 +- .../draco/src/draco/io/stdio_file_reader.cc | 7 +++ 15 files changed, 69 insertions(+), 139 deletions(-) delete mode 100644 contrib/draco/.ruby-version delete mode 100644 contrib/draco/.travis.yml delete mode 100644 contrib/draco/cmake/draco_features.cmake diff --git a/contrib/draco/.ruby-version b/contrib/draco/.ruby-version deleted file mode 100644 index 276cbf9e2..000000000 --- a/contrib/draco/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.3.0 diff --git a/contrib/draco/.travis.yml b/contrib/draco/.travis.yml deleted file mode 100644 index e9ef7123f..000000000 --- a/contrib/draco/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -cache: ccache -language: cpp -matrix: - include: - - os: linux - dist: xenial - compiler: clang - - os: linux - dist: xenial - compiler: gcc - - os: osx - compiler: clang - -addons: - apt: - packages: - - cmake - -script: - # Output version info for compilers, cmake, and make - - ${CC} -v - - ${CXX} -v - - cmake --version - - make --version - # Clone googletest - - pushd .. && git clone https://github.com/google/googletest.git && popd - # Configure and build - - mkdir _travis_build && cd _travis_build - - cmake -G "Unix Makefiles" -DENABLE_TESTS=ON .. - - make -j10 - - ./draco_tests diff --git a/contrib/draco/CMakeLists.txt b/contrib/draco/CMakeLists.txt index 3da2c664a..5526e7f60 100644 --- a/contrib/draco/CMakeLists.txt +++ b/contrib/draco/CMakeLists.txt @@ -804,7 +804,7 @@ else() draco_points_enc) # Library targets that consume the object collections. - if(MSVC OR WIN32) + if(MSVC) # In order to produce a DLL and import library the Windows tools require # 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 @@ -889,9 +889,6 @@ else() # For Mac, we need to build a .bundle for the unity plugin. if(APPLE) set_target_properties(dracodec_unity PROPERTIES BUNDLE true) - elseif(NOT unity_decoder_lib_type STREQUAL STATIC) - set_target_properties(dracodec_unity - PROPERTIES SOVERSION ${DRACO_SOVERSION}) endif() endif() @@ -916,9 +913,6 @@ else() # For Mac, we need to build a .bundle for the plugin. if(APPLE) set_target_properties(draco_maya_wrapper PROPERTIES BUNDLE true) - else() - set_target_properties(draco_maya_wrapper - PROPERTIES SOVERSION ${DRACO_SOVERSION}) endif() endif() diff --git a/contrib/draco/README.md b/contrib/draco/README.md index add66edcb..0d980b387 100644 --- a/contrib/draco/README.md +++ b/contrib/draco/README.md @@ -2,16 +2,16 @@

-![Build Status: master](https://travis-ci.org/google/draco.svg?branch=master) +[![Build Status](https://github.com/google/draco/workflows/Build/badge.svg)](https://github.com/google/draco/actions?query=workflow%3ABuild) News ======= ### Version 1.4.1 release -* Using the versioned gstatic.com WASM and Javascript decoders is now +* Using the versioned www.gstatic.com WASM and Javascript decoders is now recommended. To use v1.4.1, use this URL: * https://www.gstatic.com/draco/versioned/decoders/1.4.1/* * Replace the * with the files to load. E.g. - * https://gstatic.com/draco/versioned/decoders/1.4.1/draco_decoder.js + * https://www.gstatic.com/draco/versioned/decoders/1.4.1/draco_decoder.js * This works with the v1.3.6 and v1.4.0 releases, and will work with future Draco releases. * Bug fixes diff --git a/contrib/draco/cmake/draco_build_definitions.cmake b/contrib/draco/cmake/draco_build_definitions.cmake index c1ada6206..f7354c15f 100644 --- a/contrib/draco/cmake/draco_build_definitions.cmake +++ b/contrib/draco/cmake/draco_build_definitions.cmake @@ -6,7 +6,7 @@ set(DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_ 1) # Utility for controlling the main draco library dependency. This changes in # shared builds, and when an optional target requires a shared library build. macro(set_draco_target) - if(MSVC OR WIN32) + if(MSVC) set(draco_dependency draco) set(draco_plugin_dependency ${draco_dependency}) else() @@ -63,6 +63,11 @@ macro(draco_set_build_definitions) if(BUILD_SHARED_LIBS) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() + else() + 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() endif() if(ANDROID) @@ -114,4 +119,6 @@ macro(draco_set_build_definitions) draco_check_emscripten_environment() draco_get_required_emscripten_flags(FLAG_LIST_VAR draco_base_cxx_flags) endif() + + draco_configure_sanitizer() endmacro() diff --git a/contrib/draco/cmake/draco_features.cmake b/contrib/draco/cmake/draco_features.cmake deleted file mode 100644 index be444bf24..000000000 --- a/contrib/draco/cmake/draco_features.cmake +++ /dev/null @@ -1,63 +0,0 @@ -if(DRACO_CMAKE_DRACO_FEATURES_CMAKE_) - return() -endif() -set(DRACO_CMAKE_DRACO_FEATURES_CMAKE_ 1) - -set(draco_features_file_name "${draco_build_dir}/draco/draco_features.h") -set(draco_features_list) - -# Macro that handles tracking of Draco preprocessor symbols for the purpose of -# producing draco_features.h. -# -# draco_enable_feature(FEATURE [TARGETS ]) FEATURE -# is required. It should be a Draco preprocessor symbol. TARGETS is optional. It -# can be one or more draco targets. -# -# When the TARGETS argument is not present the preproc symbol is added to -# draco_features.h. When it is draco_features.h is unchanged, and -# target_compile_options() is called for each target specified. -macro(draco_enable_feature) - set(def_flags) - set(def_single_arg_opts FEATURE) - set(def_multi_arg_opts TARGETS) - cmake_parse_arguments(DEF "${def_flags}" "${def_single_arg_opts}" - "${def_multi_arg_opts}" ${ARGN}) - if("${DEF_FEATURE}" STREQUAL "") - message(FATAL_ERROR "Empty FEATURE passed to draco_enable_feature().") - endif() - - # Do nothing/return early if $DEF_FEATURE is already in the list. - list(FIND draco_features_list ${DEF_FEATURE} df_index) - if(NOT df_index EQUAL -1) - return() - endif() - - list(LENGTH DEF_TARGETS df_targets_list_length) - if(${df_targets_list_length} EQUAL 0) - list(APPEND draco_features_list ${DEF_FEATURE}) - else() - foreach(target ${DEF_TARGETS}) - target_compile_definitions(${target} PRIVATE ${DEF_FEATURE}) - endforeach() - endif() -endmacro() - -# Function for generating draco_features.h. -function(draco_generate_features_h) - file(WRITE "${draco_features_file_name}.new" - "// GENERATED FILE -- DO NOT EDIT\n\n" "#ifndef DRACO_FEATURES_H_\n" - "#define DRACO_FEATURES_H_\n\n") - - foreach(feature ${draco_features_list}) - file(APPEND "${draco_features_file_name}.new" "#define ${feature}\n") - endforeach() - - file(APPEND "${draco_features_file_name}.new" - "\n#endif // DRACO_FEATURES_H_") - - # Will replace ${draco_features_file_name} only if the file content has - # changed. This prevents forced Draco rebuilds after CMake runs. - configure_file("${draco_features_file_name}.new" - "${draco_features_file_name}") - file(REMOVE "${draco_features_file_name}.new") -endfunction() diff --git a/contrib/draco/cmake/draco_flags.cmake b/contrib/draco/cmake/draco_flags.cmake index cb9d489e6..0397859a4 100644 --- a/contrib/draco/cmake/draco_flags.cmake +++ b/contrib/draco/cmake/draco_flags.cmake @@ -80,6 +80,12 @@ macro(draco_test_cxx_flag) # Run the actual compile test. unset(draco_all_cxx_flags_pass CACHE) message("--- Running combined CXX flags test, flags: ${all_cxx_flags}") + + # check_cxx_compiler_flag() requires that the flags are a string. When flags + # 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) if(cxx_test_FLAG_REQUIRED AND NOT draco_all_cxx_flags_pass) @@ -194,6 +200,9 @@ macro(draco_test_exe_linker_flag) else() unset(CMAKE_EXE_LINKER_FLAGS) endif() + + list(APPEND DRACO_EXE_LINKER_FLAGS ${${link_FLAG_LIST_VAR_NAME}}) + list(REMOVE_DUPLICATES DRACO_EXE_LINKER_FLAGS) endmacro() # Runs the draco compiler tests. This macro builds up the list of list var(s) diff --git a/contrib/draco/cmake/draco_install.cmake b/contrib/draco/cmake/draco_install.cmake index 5c63ecb4a..09bfb591d 100644 --- a/contrib/draco/cmake/draco_install.cmake +++ b/contrib/draco/cmake/draco_install.cmake @@ -55,7 +55,7 @@ macro(draco_setup_install_target) install(TARGETS draco_encoder DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - if(WIN32) + if(MSVC) install(TARGETS draco DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") else() diff --git a/contrib/draco/cmake/draco_sanitizer.cmake b/contrib/draco/cmake/draco_sanitizer.cmake index ca8e23176..d2e41a6cb 100644 --- a/contrib/draco/cmake/draco_sanitizer.cmake +++ b/contrib/draco/cmake/draco_sanitizer.cmake @@ -5,28 +5,28 @@ set(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ 1) # Handles the details of enabling sanitizers. macro(draco_configure_sanitizer) - if(DRACO_SANITIZE 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 DRACO_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") - list(APPEND DRACO_EXE_LINKER_FLAGS "-flto" "-fno-sanitize-trap=cfi" + list(APPEND SAN_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") + list(APPEND SAN_LINKER_FLAGS "-flto" "-fno-sanitize-trap=cfi" "-fuse-ld=gold") endif() if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 AND DRACO_SANITIZE MATCHES "integer|undefined") - list(APPEND DRACO_EXE_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") + list(APPEND SAN_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") endif() endif() - list(APPEND DRACO_CXX_FLAGS "-fsanitize=${DRACO_SANITIZE}") - list(APPEND DRACO_EXE_LINKER_FLAGS "-fsanitize=${DRACO_SANITIZE}") + list(APPEND SAN_CXX_FLAGS "-fsanitize=${DRACO_SANITIZE}") + list(APPEND SAN_LINKER_FLAGS "-fsanitize=${DRACO_SANITIZE}") # Make sanitizer callstacks accurate. - list(APPEND DRACO_CXX_FLAGS "-fno-omit-frame-pointer" - "-fno-optimize-sibling-calls") + list(APPEND SAN_CXX_FLAGS "-fno-omit-frame-pointer") + list(APPEND SAN_CXX_FLAGS "-fno-optimize-sibling-calls") - draco_test_cxx_flag(FLAG_LIST_VAR_NAMES DRACO_CXX_FLAGS FLAG_REQUIRED) - draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME DRACO_EXE_LINKER_FLAGS) + draco_test_cxx_flag(FLAG_LIST_VAR_NAMES SAN_CXX_FLAGS FLAG_REQUIRED) + draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME SAN_LINKER_FLAGS) endif() endmacro() diff --git a/contrib/draco/cmake/draco_targets.cmake b/contrib/draco/cmake/draco_targets.cmake index 6dfa6a0c4..0456c4d7b 100644 --- a/contrib/draco/cmake/draco_targets.cmake +++ b/contrib/draco/cmake/draco_targets.cmake @@ -87,6 +87,7 @@ macro(draco_add_executable) endif() add_executable(${exe_NAME} ${exe_SOURCES}) + set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) if(exe_OUTPUT_NAME) set_target_properties(${exe_NAME} PROPERTIES OUTPUT_NAME ${exe_OUTPUT_NAME}) @@ -109,10 +110,11 @@ macro(draco_add_executable) if(exe_LINK_FLAGS OR DRACO_EXE_LINKER_FLAGS) if(${CMAKE_VERSION} VERSION_LESS "3.13") - set(link_flags ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) + 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} - ${DRACO_EXE_LINKER_FLAGS}) + PROPERTIES LINK_FLAGS "${exe_LINK_FLAGS}") else() target_link_options(${exe_NAME} PRIVATE ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) @@ -130,7 +132,7 @@ macro(draco_add_executable) endif() if(BUILD_SHARED_LIBS AND (MSVC OR WIN32)) - target_compile_definitions(${lib_NAME} PRIVATE "DRACO_BUILDING_DLL=0") + target_compile_definitions(${exe_NAME} PRIVATE "DRACO_BUILDING_DLL=0") endif() if(exe_LIB_DEPS) @@ -163,8 +165,8 @@ endmacro() # cmake-format: off # - OUTPUT_NAME: Override output file basename. Target basename defaults to # NAME. OUTPUT_NAME is ignored when BUILD_SHARED_LIBS is enabled and CMake -# is generating a build for which MSVC or WIN32 are true. This is to avoid -# output basename collisions with DLL import libraries. +# is generating a build for which MSVC is true. This is to avoid output +# basename collisions with DLL import libraries. # - TEST: Flag. Presence means treat library as a test. # - DEFINES: List of preprocessor macro definitions. # - INCLUDES: list of include directories for the target. @@ -259,7 +261,7 @@ macro(draco_add_library) endif() if(lib_OUTPUT_NAME) - if(NOT (BUILD_SHARED_LIBS AND (MSVC OR WIN32))) + if(NOT (BUILD_SHARED_LIBS AND MSVC)) set_target_properties(${lib_NAME} PROPERTIES OUTPUT_NAME ${lib_OUTPUT_NAME}) endif() @@ -318,8 +320,12 @@ macro(draco_add_library) set_target_properties(${lib_NAME} PROPERTIES PREFIX "") endif() - if(lib_TYPE STREQUAL SHARED AND NOT MSVC) - set_target_properties(${lib_NAME} PROPERTIES SOVERSION ${DRACO_SOVERSION}) + # 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}) + endif() endif() if(BUILD_SHARED_LIBS AND (MSVC OR WIN32)) diff --git a/contrib/draco/src/draco/core/cycle_timer.cc b/contrib/draco/src/draco/core/cycle_timer.cc index 94b4b28b2..58df4df77 100644 --- a/contrib/draco/src/draco/core/cycle_timer.cc +++ b/contrib/draco/src/draco/core/cycle_timer.cc @@ -17,31 +17,31 @@ namespace draco { void DracoTimer::Start() { #ifdef _WIN32 - QueryPerformanceCounter(&tv_start); + QueryPerformanceCounter(&tv_start_); #else - gettimeofday(&tv_start, nullptr); + gettimeofday(&tv_start_, nullptr); #endif } void DracoTimer::Stop() { #ifdef _WIN32 - QueryPerformanceCounter(&tv_end); + QueryPerformanceCounter(&tv_end_); #else - gettimeofday(&tv_end, nullptr); + gettimeofday(&tv_end_, nullptr); #endif } int64_t DracoTimer::GetInMs() { #ifdef _WIN32 LARGE_INTEGER elapsed = {0}; - elapsed.QuadPart = tv_end.QuadPart - tv_start.QuadPart; + elapsed.QuadPart = tv_end_.QuadPart - tv_start_.QuadPart; LARGE_INTEGER frequency = {0}; QueryPerformanceFrequency(&frequency); return elapsed.QuadPart * 1000 / frequency.QuadPart; #else - const int64_t seconds = (tv_end.tv_sec - tv_start.tv_sec) * 1000; - const int64_t milliseconds = (tv_end.tv_usec - tv_start.tv_usec) / 1000; + const int64_t seconds = (tv_end_.tv_sec - tv_start_.tv_sec) * 1000; + const int64_t milliseconds = (tv_end_.tv_usec - tv_start_.tv_usec) / 1000; return seconds + milliseconds; #endif } diff --git a/contrib/draco/src/draco/core/cycle_timer.h b/contrib/draco/src/draco/core/cycle_timer.h index 172f1c2e9..f480cc9d3 100644 --- a/contrib/draco/src/draco/core/cycle_timer.h +++ b/contrib/draco/src/draco/core/cycle_timer.h @@ -20,9 +20,10 @@ #define WIN32_LEAN_AND_MEAN #endif #include -typedef LARGE_INTEGER timeval; +typedef LARGE_INTEGER DracoTimeVal; #else #include +typedef timeval DracoTimeVal; #endif #include @@ -39,8 +40,8 @@ class DracoTimer { int64_t GetInMs(); private: - timeval tv_start; - timeval tv_end; + DracoTimeVal tv_start_; + DracoTimeVal tv_end_; }; typedef DracoTimer CycleTimer; diff --git a/contrib/draco/src/draco/io/parser_utils.cc b/contrib/draco/src/draco/io/parser_utils.cc index 4f95f6f84..12afacff6 100644 --- a/contrib/draco/src/draco/io/parser_utils.cc +++ b/contrib/draco/src/draco/io/parser_utils.cc @@ -18,6 +18,7 @@ #include #include #include +#include namespace draco { namespace parser { @@ -252,7 +253,7 @@ DecoderBuffer ParseLineIntoDecoderBuffer(DecoderBuffer *buffer) { std::string ToLower(const std::string &str) { std::string out; - std::transform(str.begin(), str.end(), std::back_inserter(out), [](unsigned char c){return tolower(c);}); + std::transform(str.begin(), str.end(), std::back_inserter(out), tolower); return out; } diff --git a/contrib/draco/src/draco/io/ply_reader.cc b/contrib/draco/src/draco/io/ply_reader.cc index cb32df225..ea7f2689a 100644 --- a/contrib/draco/src/draco/io/ply_reader.cc +++ b/contrib/draco/src/draco/io/ply_reader.cc @@ -268,14 +268,14 @@ std::vector PlyReader::SplitWords(const std::string &line) { while ((end = line.find_first_of(" \t\n\v\f\r", start)) != std::string::npos) { const std::string word(line.substr(start, end - start)); - if (!std::all_of(word.begin(), word.end(), [](unsigned char c){return isspace(c);})) { + if (!std::all_of(word.begin(), word.end(), isspace)) { output.push_back(word); } start = end + 1; } const std::string last_word(line.substr(start)); - if (!std::all_of(last_word.begin(), last_word.end(), [](unsigned char c){return isspace(c);})) { + if (!std::all_of(last_word.begin(), last_word.end(), isspace)) { output.push_back(last_word); } return output; diff --git a/contrib/draco/src/draco/io/stdio_file_reader.cc b/contrib/draco/src/draco/io/stdio_file_reader.cc index 560c3e9e8..a99c96f8f 100644 --- a/contrib/draco/src/draco/io/stdio_file_reader.cc +++ b/contrib/draco/src/draco/io/stdio_file_reader.cc @@ -87,7 +87,14 @@ size_t StdioFileReader::GetFileSize() { return false; } +#if _FILE_OFFSET_BITS == 64 + const size_t file_size = static_cast(ftello(file_)); +#elif defined _WIN64 + const size_t file_size = static_cast(_ftelli64(file_)); +#else const size_t file_size = static_cast(ftell(file_)); +#endif + rewind(file_); return file_size; From 28e34878cb9d85627cce81ed20dcb6f8b493d83b Mon Sep 17 00:00:00 2001 From: Jagoon <5785026+jagoon@users.noreply.github.com> Date: Sat, 22 May 2021 23:20:34 +0900 Subject: [PATCH 03/44] Fix fbx exporter bug if root node contains meshes. --- code/AssetLib/FBX/FBXExporter.cpp | 32 +++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index e519f7e77..b24360cd5 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -541,10 +541,17 @@ void FBXExporter::WriteReferences () // (before any actual data is written) // --------------------------------------------------------------- -size_t count_nodes(const aiNode* n) { - size_t count = 1; +size_t count_nodes(const aiNode* n, const aiNode* root) { + size_t count; + if (n == root) { + count = n->mNumMeshes; // (not counting root node) + } else if (n->mNumMeshes > 1) { + count = n->mNumMeshes + 1; + } else { + count = 1; + } for (size_t i = 0; i < n->mNumChildren; ++i) { - count += count_nodes(n->mChildren[i]); + count += count_nodes(n->mChildren[i], root); } return count; } @@ -714,7 +721,7 @@ void FBXExporter::WriteDefinitions () // Model / FbxNode // <~~ node hierarchy - count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node) + count = int32_t(count_nodes(mScene->mRootNode, mScene->mRootNode)); if (count) { n = FBX::Node("ObjectType", "Model"); n.AddChild("Count", count); @@ -2625,17 +2632,14 @@ void FBXExporter::WriteModelNodes( ], new_node_uid ); - // write model node - FBX::Node m("Model"); + + aiNode new_node; // take name from mesh name, if it exists - std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str(); - name += FBX::SEPARATOR + "Model"; - m.AddProperties(new_node_uid, name, "Mesh"); - m.AddChild("Version", int32_t(232)); - FBX::Node p("Properties70"); - p.AddP70enum("InheritType", 1); - m.AddChild(p); - m.Dump(outstream, binary, 1); + new_node.mName = mScene->mMeshes[node->mMeshes[i]]->mName; + // write model node + WriteModelNode( + outstream, binary, &new_node, new_node_uid, "Mesh", transform_chain + ); } } From f96e3cde2d299aa1bc1fd5126d55fbc4aec5cc73 Mon Sep 17 00:00:00 2001 From: Jagoon <5785026+jagoon@users.noreply.github.com> Date: Sun, 23 May 2021 00:06:05 +0900 Subject: [PATCH 04/44] Fix transform chain is applied twice --- code/AssetLib/FBX/FBXExporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index b24360cd5..f7beee2e0 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -2638,7 +2638,7 @@ void FBXExporter::WriteModelNodes( new_node.mName = mScene->mMeshes[node->mMeshes[i]]->mName; // write model node WriteModelNode( - outstream, binary, &new_node, new_node_uid, "Mesh", transform_chain + outstream, binary, &new_node, new_node_uid, "Mesh", std::vector>() ); } } From f13515a39109f717c13ee1201e9000da6eab786a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Sun, 23 May 2021 19:06:16 +0200 Subject: [PATCH 05/44] Adding basic support for lights in FBX exporter --- code/AssetLib/FBX/FBXExporter.cpp | 77 +++++++++++++++++++++++++++++-- code/AssetLib/FBX/FBXExporter.h | 2 + 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index e519f7e77..f2cae00b2 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -2196,7 +2196,65 @@ void FBXExporter::WriteObjects () bpnode.Dump(outstream, binary, indent); }*/ - // TODO: cameras, lights + // lights + indent = 1; + lights_uids.clear(); + for (size_t li = 0; li < mScene->mNumLights; ++li) { + aiLight* l = mScene->mLights[li]; + + int64_t uid = generate_uid(); + const std::string lightNodeAttributeName = l->mName.C_Str() + FBX::SEPARATOR + "NodeAttribute"; + + FBX::Node lna("NodeAttribute"); + lna.AddProperties(uid, lightNodeAttributeName, "Light"); + FBX::Node lnap("Properties70"); + + // Light color. + lnap.AddP70colorA("Color", l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b); + + // TODO Assimp light description is quite concise and do not handle light intensity. + // Default value to 1000W. + lnap.AddP70numberA("Intensity", 1000); + + // FBXLight::EType conversion + switch (l->mType) { + case aiLightSource_POINT: + lnap.AddP70enum("LightType", 0); + break; + case aiLightSource_DIRECTIONAL: + lnap.AddP70enum("LightType", 1); + break; + case aiLightSource_SPOT: + lnap.AddP70enum("LightType", 2); + lnap.AddP70numberA("InnerAngle", AI_RAD_TO_DEG(l->mAngleInnerCone)); + lnap.AddP70numberA("OuterAngle", AI_RAD_TO_DEG(l->mAngleOuterCone)); + break; + // TODO Assimp do not handle 'area' nor 'volume' lights, but FBX does. + /*case aiLightSource_AREA: + lnap.AddP70enum("LightType", 3); + lnap.AddP70enum("AreaLightShape", 0); // 0=Rectangle, 1=Sphere + break; + case aiLightSource_VOLUME: + lnap.AddP70enum("LightType", 4); + break;*/ + default: + break; + } + + // Did not understood how to configure the decay so disabling attenuation. + lnap.AddP70enum("DecayType", 0); + + // Dump to FBX stream + lna.AddChild(lnap); + lna.AddChild("TypeFlags", FBX::FBXExportProperty("Light")); + lna.AddChild("GeometryVersion", FBX::FBXExportProperty(int32_t(124))); + lna.Dump(outstream, binary, indent); + + // Store name and uid (will be used later when parsing scene nodes) + lights_uids[l->mName.C_Str()] = uid; + } + + // TODO: cameras // write nodes (i.e. model hierarchy) // start at root node @@ -2600,10 +2658,19 @@ void FBXExporter::WriteModelNodes( // and connect them connections.emplace_back("C", "OO", node_attribute_uid, node_uid); } else { - // generate a null node so we can add children to it - WriteModelNode( - outstream, binary, node, node_uid, "Null", transform_chain - ); + const auto& lightIt = lights_uids.find(node->mName.C_Str()); + if(lightIt != lights_uids.end()) { + // Node has a light connected to it. + WriteModelNode( + outstream, binary, node, node_uid, "Light", transform_chain + ); + connections.emplace_back("C", "OO", lightIt->second, node_uid); + } else { + // generate a null node so we can add children to it + WriteModelNode( + outstream, binary, node, node_uid, "Null", transform_chain + ); + } } // if more than one child mesh, make nodes for each mesh diff --git a/code/AssetLib/FBX/FBXExporter.h b/code/AssetLib/FBX/FBXExporter.h index dcd1d2727..563183268 100644 --- a/code/AssetLib/FBX/FBXExporter.h +++ b/code/AssetLib/FBX/FBXExporter.h @@ -63,6 +63,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiScene; struct aiNode; +struct aiLight; //struct aiMaterial; namespace Assimp @@ -95,6 +96,7 @@ namespace Assimp std::vector mesh_uids; std::vector material_uids; std::map node_uids; + std::map lights_uids; // this crude unique-ID system is actually fine int64_t last_uid = 999999; From 77ce4080b689d4fcee3435813c3811dfa5781682 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sun, 30 May 2021 21:54:04 +0200 Subject: [PATCH 06/44] fix viewer in case of unknown primitives. --- code/AssetLib/DXF/DXFLoader.cpp | 2 -- tools/assimp_view/assimp_view.cpp | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/code/AssetLib/DXF/DXFLoader.cpp b/code/AssetLib/DXF/DXFLoader.cpp index 5d32ed121..49d572b0b 100644 --- a/code/AssetLib/DXF/DXFLoader.cpp +++ b/code/AssetLib/DXF/DXFLoader.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, diff --git a/tools/assimp_view/assimp_view.cpp b/tools/assimp_view/assimp_view.cpp index 260b22941..5ab7c53ad 100644 --- a/tools/assimp_view/assimp_view.cpp +++ b/tools/assimp_view/assimp_view.cpp @@ -489,7 +489,7 @@ int CreateAssetData() { nidx = 3; break; default: - ai_assert(false); + CLogWindow::Instance().WriteLine("Unknown primitiv type"); break; }; @@ -500,8 +500,7 @@ int CreateAssetData() { // check whether we can use 16 bit indices if (numIndices >= 65536) { // create 32 bit index buffer - if (FAILED(g_piDevice->CreateIndexBuffer(4 * - numIndices, + if (FAILED(g_piDevice->CreateIndexBuffer(4 * numIndices, D3DUSAGE_WRITEONLY | dwUsage, D3DFMT_INDEX32, D3DPOOL_DEFAULT, @@ -523,7 +522,7 @@ int CreateAssetData() { } else { // create 16 bit index buffer if (FAILED(g_piDevice->CreateIndexBuffer(2 * - numIndices, +numIndices, D3DUSAGE_WRITEONLY | dwUsage, D3DFMT_INDEX16, D3DPOOL_DEFAULT, From 444fc9c373ae3fb407bc4bfe3a6c5558175e3fba Mon Sep 17 00:00:00 2001 From: Scott Baldric Date: Tue, 1 Jun 2021 17:08:26 -0500 Subject: [PATCH 07/44] Increasing length of mDataLength if rewriting the texture index increases magnitutde of index. --- code/Common/SceneCombiner.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp index 555d46b6a..8f10d6308 100644 --- a/code/Common/SceneCombiner.cpp +++ b/code/Common/SceneCombiner.cpp @@ -406,11 +406,25 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector, // where n is the index of the texture. - aiString &s = *((aiString *)prop->mData); + // Copy here because we overwrite the string data in-place and the buffer inside of aiString + // will be a lie if we just reinterpret from prop->mData. The size of mData is not guaranteed to be + // MAXLEN in size. + aiString s(*(aiString *)prop->mData); if ('*' == s.data[0]) { // Offset the index and write it back .. const unsigned int idx = strtoul10(&s.data[1]) + offset[n]; - ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); + const unsigned int oldLen = s.length; + + s.length = 1 + ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); + + // The string changed in size so we need to reallocate the buffer for the property. + if (oldLen < s.length) { + prop->mDataLength += s.length - oldLen; + delete[] prop->mData; + prop->mData = new char[prop->mDataLength]; + } + + memcpy(prop->mData, static_cast(&s), prop->mDataLength); } } From 121c0e7d0c9db2db6c80897b46dfc4743f64d2b7 Mon Sep 17 00:00:00 2001 From: Hill Ma Date: Mon, 7 Jun 2021 21:53:28 -0700 Subject: [PATCH 08/44] Add GetEmbeddedTextureAndIndex() to aiScene. It allows the caller to get the index of the embedded texture that is always computed anyway. --- include/assimp/scene.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/include/assimp/scene.h b/include/assimp/scene.h index 769499a27..fb3b2ef6e 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -397,22 +397,27 @@ struct aiScene //! Returns an embedded texture const aiTexture* GetEmbeddedTexture(const char* filename) const { + return GetEmbeddedTextureAndIndex(filename).first; + } + + //! Returns an embedded texture and its index + std::pair GetEmbeddedTextureAndIndex(const char* filename) const { // lookup using texture ID (if referenced like: "*1", "*2", etc.) if ('*' == *filename) { int index = std::atoi(filename + 1); if (0 > index || mNumTextures <= static_cast(index)) - return nullptr; - return mTextures[index]; + return std::make_pair(nullptr, -1); + return std::make_pair(mTextures[index], index); } // lookup using filename const char* shortFilename = GetShortFilename(filename); for (unsigned int i = 0; i < mNumTextures; i++) { const char* shortTextureFilename = GetShortFilename(mTextures[i]->mFilename.C_Str()); if (strcmp(shortTextureFilename, shortFilename) == 0) { - return mTextures[i]; + return std::make_pair(mTextures[i], i); } } - return nullptr; + return std::make_pair(nullptr, -1); } #endif // __cplusplus From ef739c170312a1667e1b8fd2e54a2d1614631a56 Mon Sep 17 00:00:00 2001 From: Hill Ma Date: Tue, 8 Jun 2021 12:53:18 -0700 Subject: [PATCH 09/44] glTF2: Make handling of embedded textures safer. Previous code does not check whether the embedded texture exists. --- code/AssetLib/glTF2/glTF2Exporter.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 751508225..83356f7c2 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -118,14 +118,14 @@ glTF2Exporter::glTF2Exporter(const char* filename, IOSystem* pIOSystem, const ai ExportScene(); ExportAnimations(); - + // export extras if(mProperties->HasPropertyCallback("extras")) { std::function ExportExtras = mProperties->GetPropertyCallback("extras"); mAsset->extras = (rapidjson::Value*)ExportExtras(0); } - + AssetWriter writer(*mAsset); if (isBinary) { @@ -515,11 +515,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe std::string imgId = mAsset->FindUniqueID("", "image"); texture->source = mAsset->images.Create(imgId); - if (path[0] == '*') { // embedded - aiTexture* curTex = mScene->mTextures[atoi(&path[1])]; - + const aiTexture* curTex = mScene->GetEmbeddedTexture(path.c_str()); + if (curTex != nullptr) { // embedded texture->source->name = curTex->mFilename.C_Str(); - + //basisu: embedded ktx2, bu if (curTex->achFormatHint[0]) { std::string mimeType = "image/"; @@ -541,7 +540,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe mimeType += curTex->achFormatHint; texture->source->mimeType = mimeType; } - + // The asset has its own buffer, see Image::SetData //basisu: "image/ktx2", "image/basis" as is texture->source->SetData(reinterpret_cast(curTex->pcData), curTex->mWidth, *mAsset); @@ -554,7 +553,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe useBasisUniversal = true; } } - + //basisu if(useBasisUniversal) { mAsset->extensionsUsed.KHR_texture_basisu = true; From 9f9d77f882efc445f46139ed2188ce21fc857032 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:13:46 +0100 Subject: [PATCH 10/44] First pass at simplifying glTFv2 PBR Removed 'core' set of GLTF-specific properties --- code/AssetLib/glTF2/glTF2Exporter.cpp | 60 ++++++++++++++------------- code/AssetLib/glTF2/glTF2Importer.cpp | 21 ++++++---- include/assimp/material.h | 40 +++++++++++++++++- include/assimp/pbrmaterial.h | 14 +++---- test/unit/utglTF2ImportExport.cpp | 55 ++++++++++++++++++++---- 5 files changed, 138 insertions(+), 52 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 751508225..dfa62372a 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -645,7 +645,7 @@ void glTF2Exporter::ExportMaterials() m->name = name; - GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + 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 @@ -654,19 +654,19 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR) != AI_SUCCESS) { + 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 GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + 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 m->pbrMetallicRoughness.metallicFactor = 0; } // get roughness if source is gltf2 file - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { // otherwise, try to derive and convert from specular + shininess values aiColor4D specularColor; ai_real shininess; @@ -712,36 +712,38 @@ void glTF2Exporter::ExportMaterials() } } - bool hasPbrSpecularGlossiness = false; - mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS, hasPbrSpecularGlossiness); - - if (hasPbrSpecularGlossiness) { - - if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { - mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; - } - + { + // If has a Specular color, use the KHR_materials_pbrSpecularGlossiness extension PbrSpecularGlossiness pbrSG; - - GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); - GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR); - - if (mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { - float shininess; - - if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = shininess / 1000; + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; } + + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + + // If don't have explicit glossiness then convert from roughness or shininess + if (mat->Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { + float shininess; + if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + // Add any appropriate textures + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + m->pbrSpecularGlossiness = Nullable(pbrSG); } - - GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); - GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - - m->pbrSpecularGlossiness = Nullable(pbrSG); } - bool unlit; - if (mat->Get(AI_MATKEY_GLTF_UNLIT, unlit) == AI_SUCCESS && unlit) { + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; + mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode); + if (shadingMode == aiShadingMode_Unlit) { mAsset->extensionsUsed.KHR_materials_unlit = true; m->unlit = true; } diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index c62989c3b..b5ba65857 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -238,16 +238,18 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&str, AI_MATKEY_NAME); } + // Set Assimp DIFFUSE and BASE COLOR to the pbrMetallicRoughness base color and texture for backwards compatibility + // Technically should not load any pbrMetallicRoughness if extensionsRequired contains KHR_materials_pbrSpecularGlossiness SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); - aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_ROUGHNESS_FACTOR); float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; roughnessAsShininess *= roughnessAsShininess * 1000; @@ -268,22 +270,27 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M if (mat.pbrSpecularGlossiness.isPresent) { PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; - aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS); SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); - aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLOSSINESS_FACTOR); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); } + + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; if (mat.unlit) { - aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); + shadingMode = aiShadingMode_Unlit; } + aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); + + //KHR_materials_sheen if (mat.materialSheen.isPresent) { MaterialSheen &sheen = mat.materialSheen.value; diff --git a/include/assimp/material.h b/include/assimp/material.h index 08c0491c0..11cdef1f4 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -202,11 +202,15 @@ enum aiTextureType { /** The texture is combined with the result of the diffuse * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_DIFFUSE = 1, /** The texture is combined with the result of the specular * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_SPECULAR = 2, @@ -309,7 +313,9 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); // --------------------------------------------------------------------------- /** @brief Defines all shading models supported by the library - * + * + * #AI_MATKEY_SHADING_MODEL + * * The list of shading modes has been taken from Blender. * See Blender documentation for more information. The API does * not distinguish between "specular" and "diffuse" shaders (thus the @@ -364,13 +370,27 @@ enum aiShadingMode { aiShadingMode_CookTorrance = 0x8, /** No shading at all. Constant light influence of 1.0. + * Also known as "Unlit" */ aiShadingMode_NoShading = 0x9, + aiShadingMode_Unlit = aiShadingMode_NoShading, // Alias /** Fresnel shading */ aiShadingMode_Fresnel = 0xa, + /** Physically-Based Rendering (PBR) shading using + * Bidirectional scattering/reflectance distribution function (BSDF/BRDF) + * There are multiple methods under this banner, and model files may provide + * data for more than one PBR-BRDF method. + * Applications should use the set of provided properties to determine which + * of their preferred PBDR methods are available + * eg: + * - If AI_MATKEY_METALLIC_FACTOR is set, then a Metallic/Roughness is available + * - If AI_MATKEY_COLOR_SPECULAR is set, then a Specular/Glossiness is available + */ + aiShadingMode_PBR_BRDF = 0xb, + #ifndef SWIG _aiShadingMode_Force32Bit = INT_MAX #endif @@ -923,11 +943,29 @@ extern "C" { // --------------------------------------------------------------------------- // PBR material support #define AI_MATKEY_USE_COLOR_MAP "$mat.useColorMap", 0, 0 + +// Metallic/Roughness Workflow +// --------------------------- +// Base color factor. Will be multiplied by final base color texture values if extant #define AI_MATKEY_BASE_COLOR "$clr.base", 0, 0 #define AI_MATKEY_USE_METALLIC_MAP "$mat.useMetallicMap", 0, 0 +// Metallic factor. 0.0 = Full Dielectric, 1.0 = Full Metal #define AI_MATKEY_METALLIC_FACTOR "$mat.metallicFactor", 0, 0 #define AI_MATKEY_USE_ROUGHNESS_MAP "$mat.useRoughnessMap", 0, 0 +// Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 + +// Specular/Glossiness Workflow +// --------------------------- +// Diffuse/Albedo Color. Note: Pure Metals have a diffuse of {0,0,0} +// AI_MATKEY_COLOR_DIFFUSE +// Specular Color +// AI_MATKEY_COLOR_SPECULAR +// Glossiness factor. 0.0 = Completely Rough, 1.0 = Perfectly Smooth +#define AI_MATKEY_GLOSSINESS_FACTOR "$mat.glossinessFactor", 0, 0 + +// Emissive +// -------- #define AI_MATKEY_USE_EMISSIVE_MAP "$mat.useEmissiveMap", 0, 0 #define AI_MATKEY_EMISSIVE_INTENSITY "$mat.emissiveIntensity", 0, 0 #define AI_MATKEY_USE_AO_MAP "$mat.useAOMap", 0, 0 diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index 2e41b8b6d..fd904e4fc 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -50,16 +50,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # pragma GCC system_header #endif -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE aiTextureType_DIFFUSE, 1 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE aiTextureType_DIFFUSE, 1 #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 0 #define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0 #define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0 -#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 -#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 -#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 +//#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 +//#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 +//#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 4110edcfc..e0ac10ad5 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -57,10 +57,9 @@ using namespace Assimp; class utglTF2ImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + virtual bool importerMatTest(const char *file, bool spec_gloss, std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure); EXPECT_NE(scene, nullptr); if (!scene) { return false; @@ -72,13 +71,49 @@ public: } const aiMaterial *material = scene->mMaterials[0]; + // This Material should be a PBR + aiShadingMode shadingMode; + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_SHADING_MODEL, shadingMode)); + EXPECT_EQ(aiShadingMode_PBR_BRDF, shadingMode); + + // Should import the texture as diffuse and as base color aiString path; - aiTextureMapMode modes[2]; + std::array modes; EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + nullptr, nullptr, modes.data())); EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); - EXPECT_EQ(modes[0], aiTextureMapMode_Mirror); - EXPECT_EQ(modes[1], aiTextureMapMode_Clamp); + EXPECT_EQ(exp_modes, modes); + + // Also as Base Color + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_BASE_COLOR, 0, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); + EXPECT_EQ(exp_modes, modes); + + // Should have a MetallicFactor (default is 1.0) + ai_real metal_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_METALLIC_FACTOR, metal_factor)); + EXPECT_EQ(ai_real(0.0), metal_factor); + + // And a roughness factor (default is 1.0) + ai_real roughness_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness_factor)); + EXPECT_EQ(ai_real(1.0), roughness_factor); + + aiColor3D spec_color = { 0, 0, 0 }; + ai_real glossiness = ai_real(0.5); + if (spec_gloss) { + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + constexpr ai_real spec_val(0.20000000298023225); // From the file + EXPECT_EQ(spec_val, spec_color.r); + EXPECT_EQ(spec_val, spec_color.g); + EXPECT_EQ(spec_val, spec_color.b); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + EXPECT_EQ(ai_real(1.0), glossiness); + } else { + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + } return true; } @@ -105,13 +140,17 @@ public: }; TEST_F(utglTF2ImportExport, importglTF2FromFileTest) { - EXPECT_TRUE(importerTest()); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", false, {aiTextureMapMode_Mirror, aiTextureMapMode_Clamp})); } TEST_F(utglTF2ImportExport, importBinaryglTF2FromFileTest) { EXPECT_TRUE(binaryImporterTest()); } +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); +} + #ifndef ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; From 6dd9ab062c0404383fb6ebc034687b71a482c7a8 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Fri, 11 Jun 2021 10:56:45 +0300 Subject: [PATCH 11/44] the expression does not throw an exception. maybe you just forgot this word. --- contrib/poly2tri/poly2tri/sweep/sweep.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/poly2tri/poly2tri/sweep/sweep.cc b/contrib/poly2tri/poly2tri/sweep/sweep.cc index 23aeb6b57..8e3d794c0 100644 --- a/contrib/poly2tri/poly2tri/sweep/sweep.cc +++ b/contrib/poly2tri/poly2tri/sweep/sweep.cc @@ -129,7 +129,7 @@ void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangl EdgeEvent( tcx, ep, *p1, triangle, *p1 ); } else { // ASSIMP_CHANGE (aramis_acg) - std::runtime_error("EdgeEvent - collinear points not supported"); + throw std::runtime_error("EdgeEvent - collinear points not supported"); } return; } From 4b4d6b13268bda0c16bc5ba593b8b2abb81b1d3a Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 13:44:52 +0100 Subject: [PATCH 12/44] Standardise Clearcoat, Sheen and Transmission Also cleanup glTFv2 defaults, don't import/export if disabled --- code/AssetLib/glTF2/glTF2Exporter.cpp | 236 +++++++++++++++----------- code/AssetLib/glTF2/glTF2Exporter.h | 26 ++- code/AssetLib/glTF2/glTF2Importer.cpp | 39 ++--- code/Common/material.cpp | 12 +- include/assimp/material.h | 77 ++++++++- include/assimp/pbrmaterial.h | 28 +-- test/unit/utglTF2ImportExport.cpp | 15 ++ 7 files changed, 281 insertions(+), 152 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index dfa62372a..3cb3891b0 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -436,11 +436,11 @@ inline void SetSamplerWrap(SamplerWrap& wrap, aiTextureMapMode map) }; } -void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetTexSampler(const aiMaterial& mat, Ref texture, aiTextureType tt, unsigned int slot) { aiString aId; std::string id; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { id = aId.C_Str(); } @@ -455,49 +455,49 @@ void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, a SamplerMagFilter filterMag; SamplerMinFilter filterMin; - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapS, mapU); } - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapT, mapV); } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { texture->sampler->magFilter = filterMag; } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { texture->sampler->minFilter = filterMin; } aiString name; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { texture->sampler->name = name.C_Str(); } } } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) { - if (mat->GetTextureCount(tt) > 0) { + if (mat.GetTextureCount(tt) > 0) { aiString tex; - if (mat->Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); if (path.size() > 0) { @@ -568,7 +568,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -579,7 +579,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, TextureInfo& prop, aiTextur } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -591,7 +591,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, NormalTextureInfo& prop, ai } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -603,10 +603,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, OcclusionTextureInfo& prop, } } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec4& prop, const char* propName, int type, int idx) const { aiColor4D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; prop[3] = col.a; @@ -615,30 +615,109 @@ aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const cha return result; } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec3& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec3& prop, const char* propName, int type, int idx) const { aiColor3D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { - prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; + prop[0] = col.r; + prop[1] = col.g; + prop[2] = col.b; } return result; } +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 { + // Don't have explicit glossiness, convert from pbr roughness or legacy shininess + float shininess; + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + result = true; + } + // Add any appropriate textures + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + result == result || pbrSG.specularGlossinessTexture.texture; + + if (result) { + // Likely to always have diffuse + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + } + + return result; +} + +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) + return false; + + // Default Sheen color factor {0,0,0} disables Sheen, so do not export + if (sheen.sheenColorFactor == defaultSheenFactor) + return false; + + mat.Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); + + GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_SHEEN_COLOR_TEXTURE); + GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat) { + if (mat.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor) != aiReturn_SUCCESS) { + return false; + } + + // Clearcoat factor of zero disables Clearcoat, so do not export + if (clearcoat.clearcoatFactor == 0.0f) + return false; + + mat.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); + + GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_CLEARCOAT_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission) { + bool result = mat.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmission.transmissionFactor) == aiReturn_SUCCESS; + GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_TRANSMISSION_TEXTURE); + return result || transmission.transmissionTexture.texture; +} + void glTF2Exporter::ExportMaterials() { aiString aiName; for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { - const aiMaterial* mat = mScene->mMaterials[i]; + ai_assert(mScene->mMaterials[i] != nullptr); + + const aiMaterial & mat = *(mScene->mMaterials[i]); std::string id = "material_" + ai_to_string(i); Ref m = mAsset->materials.Create(id); std::string name; - if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { name = aiName.C_Str(); } name = mAsset->FindUniqueID(name, "material"); @@ -660,20 +739,20 @@ void glTF2Exporter::ExportMaterials() GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } - if (mat->Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + 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 m->pbrMetallicRoughness.metallicFactor = 0; } // get roughness if source is gltf2 file - if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { // otherwise, try to derive and convert from specular + shininess values aiColor4D specularColor; ai_real shininess; if ( - mat->Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && - mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS + 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; @@ -694,17 +773,17 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->emissiveTexture, aiTextureType_EMISSIVE); GetMatColor(mat, m->emissiveFactor, AI_MATKEY_COLOR_EMISSIVE); - mat->Get(AI_MATKEY_TWOSIDED, m->doubleSided); - mat->Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided); + mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); aiString alphaMode; - if (mat->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { m->alphaMode = alphaMode.C_Str(); } else { float opacity; - if (mat->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { if (opacity < 1) { m->alphaMode = "BLEND"; m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; @@ -713,86 +792,43 @@ void glTF2Exporter::ExportMaterials() } { - // If has a Specular color, use the KHR_materials_pbrSpecularGlossiness extension + // KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020) PbrSpecularGlossiness pbrSG; - if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { - if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { - mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; - } - - GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); - - // If don't have explicit glossiness then convert from roughness or shininess - if (mat->Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { - float shininess; - if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way - } else if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = shininess / 1000; - } - } - - // Add any appropriate textures - GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); - GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - + if (GetMatSpecGloss(mat, pbrSG)) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; m->pbrSpecularGlossiness = Nullable(pbrSG); } } // glTFv2 is either PBR or Unlit aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; - mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode); + mat.Get(AI_MATKEY_SHADING_MODEL, shadingMode); if (shadingMode == aiShadingMode_Unlit) { mAsset->extensionsUsed.KHR_materials_unlit = true; m->unlit = true; - } + } else { + // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness + if (!m->pbrSpecularGlossiness.isPresent) { + // Sheen + MaterialSheen sheen; + if (GetMatSheen(mat, sheen)) { + mAsset->extensionsUsed.KHR_materials_sheen = true; + m->materialSheen = Nullable(sheen); + } - bool hasMaterialSheen = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN, hasMaterialSheen); + MaterialClearcoat clearcoat; + if (GetMatClearcoat(mat, clearcoat)) { + mAsset->extensionsUsed.KHR_materials_clearcoat = true; + m->materialClearcoat = Nullable(clearcoat); + } - if (hasMaterialSheen) { - mAsset->extensionsUsed.KHR_materials_sheen = true; - - MaterialSheen sheen; - - GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); - GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); - - m->materialSheen = Nullable(sheen); - } - - bool hasMaterialClearcoat = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT, hasMaterialClearcoat); - - if (hasMaterialClearcoat) { - mAsset->extensionsUsed.KHR_materials_clearcoat= true; - - MaterialClearcoat clearcoat; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor); - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); - GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); - - m->materialClearcoat = Nullable(clearcoat); - } - - bool hasMaterialTransmission = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION, hasMaterialTransmission); - - if (hasMaterialTransmission) { - mAsset->extensionsUsed.KHR_materials_transmission = true; - - MaterialTransmission transmission; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR, transmission.transmissionFactor); - GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); - - m->materialTransmission = Nullable(transmission); + MaterialTransmission transmission; + if (GetMatTransmission(mat, transmission)) { + mAsset->extensionsUsed.KHR_materials_transmission = true; + m->materialTransmission = Nullable(transmission); + } + } } } } diff --git a/code/AssetLib/glTF2/glTF2Exporter.h b/code/AssetLib/glTF2/glTF2Exporter.h index 86497516a..edc85d998 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.h +++ b/code/AssetLib/glTF2/glTF2Exporter.h @@ -72,6 +72,10 @@ namespace glTF2 struct OcclusionTextureInfo; struct Node; struct Texture; + struct PbrSpecularGlossiness; + struct MaterialSheen; + struct MaterialClearcoat; + struct MaterialTransmission; // Vec/matrix types, as raw float arrays typedef float (vec2)[2]; @@ -97,15 +101,19 @@ namespace Assimp protected: void WriteBinaryData(IOStream* outfile, std::size_t sceneLength); - void GetTexSampler(const aiMaterial* mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); - void GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTex(const aiMaterial* mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec4& prop, const char* propName, int type, int idx); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec3& prop, const char* propName, int type, int idx); + void GetTexSampler(const aiMaterial& mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); + void GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); + 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 GetMatSheen(const aiMaterial& mat, glTF2::MaterialSheen& sheen); + bool GetMatClearcoat(const aiMaterial& mat, glTF2::MaterialClearcoat& clearcoat); + bool GetMatTransmission(const aiMaterial& mat, glTF2::MaterialTransmission& transmission); void ExportMetadata(); void ExportMaterials(); void ExportMeshes(); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b5ba65857..b0f0955f5 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -291,36 +291,37 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); - //KHR_materials_sheen + // KHR_materials_sheen if (mat.materialSheen.isPresent) { MaterialSheen &sheen = mat.materialSheen.value; - - aimat->AddProperty(&mat.materialSheen.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN); - SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); + // Default value {0,0,0} disables Sheen + if (sheen.sheenColorFactor != defaultSheenFactor) { + SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_SHEEN_COLOR_FACTOR); + aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_SHEEN_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_SHEEN_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + } } - //KHR_materials_clearcoat + // KHR_materials_clearcoat if (mat.materialClearcoat.isPresent) { MaterialClearcoat &clearcoat = mat.materialClearcoat.value; - - aimat->AddProperty(&mat.materialClearcoat.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT); - aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR); - aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); + // Default value 0.0 disables clearcoat + if (clearcoat.clearcoatFactor != 0.0f) { + aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_CLEARCOAT_FACTOR); + aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_CLEARCOAT_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + } } - //KHR_materials_transmission + // KHR_materials_transmission if (mat.materialTransmission.isPresent) { MaterialTransmission &transmission = mat.materialTransmission.value; - aimat->AddProperty(&mat.materialTransmission.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION); - aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); + aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_TRANSMISSION_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_TRANSMISSION_TEXTURE); } return aimat; diff --git a/code/Common/material.cpp b/code/Common/material.cpp index 5230c8b43..6c90e66f0 100644 --- a/code/Common/material.cpp +++ b/code/Common/material.cpp @@ -47,10 +47,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // ------------------------------------------------------------------------------- -const char* TextureTypeToString(aiTextureType in) -{ - switch (in) - { +const char *TextureTypeToString(aiTextureType in) { + switch (in) { case aiTextureType_NONE: return "n/a"; case aiTextureType_DIFFUSE: @@ -87,6 +85,12 @@ const char* TextureTypeToString(aiTextureType in) return "DiffuseRoughness"; case aiTextureType_AMBIENT_OCCLUSION: return "AmbientOcclusion"; + case aiTextureType_SHEEN: + return "Sheen"; + case aiTextureType_CLEARCOAT: + return "Clearcoat"; + case aiTextureType_TRANSMISSION: + return "Transmission"; case aiTextureType_UNKNOWN: return "Unknown"; default: diff --git a/include/assimp/material.h b/include/assimp/material.h index 11cdef1f4..33e39529e 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -144,7 +144,9 @@ enum aiTextureMapMode { enum aiTextureMapping { /** The mapping coordinates are taken from an UV channel. * - * The #AI_MATKEY_UVWSRC key specifies from which UV channel + * #AI_MATKEY_UVWSRC property + * + * Specifies from which UV channel * the texture coordinates are to be taken from (remember, * meshes can have more than one UV channel). */ @@ -292,6 +294,32 @@ enum aiTextureType { aiTextureType_DIFFUSE_ROUGHNESS = 16, aiTextureType_AMBIENT_OCCLUSION = 17, + /** PBR Material Modifiers + * Some modern renderers have further PBR modifiers that may be overlaid + * on top of the 'base' PBR materials for additional realism. + * These use multiple texture maps, so only the base type is directly defined + */ + + /** Sheen + * Generally used to simulate textiles that are covered in a layer of microfibers + * eg velvet + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_sheen + */ + aiTextureType_SHEEN = 19, + + /** Clearcoat + * Simulates a layer of 'polish' or 'laquer' layered on top of a PBR substrate + * https://autodesk.github.io/standard-surface/#closures/coating + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ + aiTextureType_CLEARCOAT = 20, + + /** Transmission + * Simulates transmission through the surface + * May include further information such as wall thickness + */ + aiTextureType_TRANSMISSION = 21, + /** Unknown texture * * A texture reference that does not match any of the definitions @@ -314,7 +342,7 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); // --------------------------------------------------------------------------- /** @brief Defines all shading models supported by the library * - * #AI_MATKEY_SHADING_MODEL + * Property: #AI_MATKEY_SHADING_MODEL * * The list of shading modes has been taken from Blender. * See Blender documentation for more information. The API does @@ -324,6 +352,7 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); * Again, this value is just a hint. Assimp tries to select the shader whose * most common implementation matches the original rendering results of the * 3D modeler which wrote a particular model as closely as possible. + * */ enum aiShadingMode { /** Flat shading. Shading is done on per-face base, @@ -384,10 +413,11 @@ enum aiShadingMode { * There are multiple methods under this banner, and model files may provide * data for more than one PBR-BRDF method. * Applications should use the set of provided properties to determine which - * of their preferred PBDR methods are available + * of their preferred PBR rendering methods are likely to be available * eg: * - If AI_MATKEY_METALLIC_FACTOR is set, then a Metallic/Roughness is available - * - If AI_MATKEY_COLOR_SPECULAR is set, then a Specular/Glossiness is available + * - If AI_MATKEY_GLOSSINESS_FACTOR is set, then a Specular/Glossiness is available + * Note that some PBR methods allow layering of techniques */ aiShadingMode_PBR_BRDF = 0xb, @@ -942,28 +972,63 @@ extern "C" { // --------------------------------------------------------------------------- // PBR material support +// -------------------- +// Properties defining PBR rendering techniques #define AI_MATKEY_USE_COLOR_MAP "$mat.useColorMap", 0, 0 // Metallic/Roughness Workflow // --------------------------- -// Base color factor. Will be multiplied by final base color texture values if extant +// Base RGBA color factor. Will be multiplied by final base color texture values if extant +// Note: Importers may choose to copy this into AI_MATKEY_COLOR_DIFFUSE for compatibility +// with renderers and formats that do not support Metallic/Roughness PBR #define AI_MATKEY_BASE_COLOR "$clr.base", 0, 0 +#define AI_MATKEY_BASE_COLOR_TEXTURE aiTextureType_BASE_COLOR, 0 #define AI_MATKEY_USE_METALLIC_MAP "$mat.useMetallicMap", 0, 0 // Metallic factor. 0.0 = Full Dielectric, 1.0 = Full Metal #define AI_MATKEY_METALLIC_FACTOR "$mat.metallicFactor", 0, 0 +#define AI_MATKEY_METALLIC_TEXTURE aiTextureType_METALNESS, 0 #define AI_MATKEY_USE_ROUGHNESS_MAP "$mat.useRoughnessMap", 0, 0 // Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 +#define AI_MATKEY_ROUGHNESS_TEXTURE aiTextureType_DIFFUSE_ROUGHNESS, 0 // Specular/Glossiness Workflow // --------------------------- // Diffuse/Albedo Color. Note: Pure Metals have a diffuse of {0,0,0} // AI_MATKEY_COLOR_DIFFUSE -// Specular Color +// Specular Color. +// Note: Metallic/Roughness may also have a Specular Color // AI_MATKEY_COLOR_SPECULAR +#define AI_MATKEY_SPECULAR_FACTOR "$mat.specularFactor", 0, 0 // Glossiness factor. 0.0 = Completely Rough, 1.0 = Perfectly Smooth #define AI_MATKEY_GLOSSINESS_FACTOR "$mat.glossinessFactor", 0, 0 +// Sheen +// ----- +// Sheen base RGB color. Default {0,0,0} +#define AI_MATKEY_SHEEN_COLOR_FACTOR "$clr.sheen.factor", 0, 0 +// Sheen Roughness Factor. +#define AI_MATKEY_SHEEN_ROUGHNESS_FACTOR "$mat.sheen.roughnessFactor", 0, 0 +#define AI_MATKEY_SHEEN_COLOR_TEXTURE aiTextureType_SHEEN, 0 +#define AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE aiTextureType_SHEEN, 1 + +// Clearcoat +// --------- +#define AI_MATKEY_CLEARCOAT_FACTOR "$clr.clearcoat.factor", 0, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR "$mat.clearcoat.roughnessFactor", 0, 0 +#define AI_MATKEY_CLEARCOAT_TEXTURE aiTextureType_CLEARCOAT, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_CLEARCOAT, 1 +#define AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE aiTextureType_CLEARCOAT, 2 + +// Transmission +// ------------ +// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission +// Base percentage of light transmitted through the surface. 0.0 = Opaque, 1.0 = Fully transparent +#define AI_MATKEY_TRANSMISSION_FACTOR "$mat.transmission.factor", 0, 0 +// Texture defining percentage of light transmitted through the surface. +// Multiplied by AI_MATKEY_TRANSMISSION_FACTOR +#define AI_MATKEY_TRANSMISSION_TEXTURE aiTextureType_TRANSMISSION, 0 + // Emissive // -------- #define AI_MATKEY_USE_EMISSIVE_MAP "$mat.useEmissiveMap", 0, 0 diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index fd904e4fc..c67bcc3b8 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -60,20 +60,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 //#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 //#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE aiTextureType_UNKNOWN, 1 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 2 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT "$mat.gltf.materialClearcoat", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR "$mat.gltf.materialClearcoat.clearcoatFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR "$mat.gltf.materialClearcoat.clearcoatRoughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE aiTextureType_UNKNOWN, 3 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 4 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE aiTextureType_NORMALS, 1 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION "$mat.gltf.materialTransmission", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE aiTextureType_UNKNOWN, 1 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 2 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT "$mat.gltf.materialClearcoat", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR "$mat.gltf.materialClearcoat.clearcoatFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR "$mat.gltf.materialClearcoat.clearcoatRoughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE aiTextureType_UNKNOWN, 3 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 4 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE aiTextureType_NORMALS, 1 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION "$mat.gltf.materialTransmission", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 #define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" #define _AI_MATKEY_GLTF_MAPPINGNAME_BASE "$tex.mappingname" diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index e0ac10ad5..766372325 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -152,6 +152,20 @@ TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { } #ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossiness) { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb")); + + // And re-import + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true)); +} + TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; Assimp::Exporter exporter; @@ -169,6 +183,7 @@ TEST_F(utglTF2ImportExport, importglTF2EmbeddedAndExportToOBJ) { EXPECT_NE(nullptr, scene); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured_out.obj")); } + #endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, importglTF2PrimitiveModePointsWithoutIndices) { From c86522e552dc3edf0d2a3cff228956f3ce87b5c0 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 14:34:42 +0100 Subject: [PATCH 13/44] Add glTFv2 Clearcoat import/export tests Uses Clearcoat model from Khronos --- code/AssetLib/glTF2/glTF2Importer.cpp | 5 + include/assimp/material.h | 3 +- .../glTF2/ClearCoat-glTF/ClearCoatLabels.png | Bin 0 -> 10270 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.bin | Bin 0 -> 50328 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.gltf | 1669 +++++++++++++++++ .../glTF2/ClearCoat-glTF/PartialCoating.png | Bin 0 -> 5077 bytes .../ClearCoat-glTF/PartialCoating_Alpha.png | Bin 0 -> 5065 bytes .../ClearCoat-glTF/PlasticWrap_normals.jpg | Bin 0 -> 144210 bytes .../glTF2/ClearCoat-glTF/RibsNormal.png | Bin 0 -> 1605 bytes .../glTF2/ClearCoat-glTF/RoughnessStripes.png | Bin 0 -> 5033 bytes test/unit/utglTF2ImportExport.cpp | 57 + 11 files changed, 1733 insertions(+), 1 deletion(-) create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg create mode 100644 test/models/glTF2/ClearCoat-glTF/RibsNormal.png create mode 100644 test/models/glTF2/ClearCoat-glTF/RoughnessStripes.png diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b0f0955f5..b435f111d 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -208,6 +208,11 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset if (sampler->minFilter != SamplerMinFilter::UNSET) { mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); } + } else { + // Use glTFv2 default sampler + const aiTextureMapMode default_wrap = aiTextureMapMode_Wrap; + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); } } } diff --git a/include/assimp/material.h b/include/assimp/material.h index 33e39529e..f348da369 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -1014,7 +1014,8 @@ extern "C" { // Clearcoat // --------- -#define AI_MATKEY_CLEARCOAT_FACTOR "$clr.clearcoat.factor", 0, 0 +// Clearcoat layer intensity. 0.0 = none (disabled) +#define AI_MATKEY_CLEARCOAT_FACTOR "$mat.clearcoat.factor", 0, 0 #define AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR "$mat.clearcoat.roughnessFactor", 0, 0 #define AI_MATKEY_CLEARCOAT_TEXTURE aiTextureType_CLEARCOAT, 0 #define AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_CLEARCOAT, 1 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png b/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png new file mode 100644 index 0000000000000000000000000000000000000000..d47f2f5e1f06116fca5ddd9697001ffedcc5bd68 GIT binary patch literal 10270 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 literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7c6a93a4995917f931d580f7d25c94f33d1639a GIT binary patch literal 50328 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 z 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 MV6NHY-@g5S0BBn6`Tzg` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf new file mode 100644 index 000000000..ae12bfc11 --- /dev/null +++ b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf @@ -0,0 +1,1669 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.2.8 with hand-edits for clearcoat", + "version" : "2.0" + }, + "extensionsUsed": [ + "KHR_materials_clearcoat" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 3, + 7, + 11, + 15, + 19, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 1, + "name" : "ClearCoatSample" + }, + { + "mesh" : 2, + "name" : "CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 0, + 1, + 2 + ], + "name" : "R0_SimpleCoatTest", + "translation" : [ + 0, + 5.25, + 0 + ] + }, + { + "mesh" : 3, + "name" : "R1_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 4, + "name" : "R1_ClearCoatSample" + }, + { + "mesh" : 5, + "name" : "R1_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 4, + 5, + 6 + ], + "name" : "R1_PartialCoatTest", + "translation" : [ + 0, + 3.1500000953674316, + 0 + ] + }, + { + "mesh" : 6, + "name" : "R2_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 7, + "name" : "R2_ClearCoatSample" + }, + { + "mesh" : 8, + "name" : "R2_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 8, + 9, + 10 + ], + "name" : "R2_RoughnessVariations", + "translation" : [ + 0, + 1.0499999523162842, + 0 + ] + }, + { + "mesh" : 9, + "name" : "R3_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 10, + "name" : "R3_ClearCoatSample" + }, + { + "mesh" : 11, + "name" : "R3_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 12, + 13, + 14 + ], + "name" : "R3_BaseNormals", + "translation" : [ + 0, + -1.0499999523162842, + 0 + ] + }, + { + "mesh" : 12, + "name" : "R4_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 13, + "name" : "R4_ClearCoatSample" + }, + { + "mesh" : 14, + "name" : "R4_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 16, + 17, + 18 + ], + "name" : "R4_CoatNormals", + "translation" : [ + 0, + -5.25, + 0 + ] + }, + { + "mesh" : 15, + "name" : "R5_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 16, + "name" : "R5_ClearCoatSample" + }, + { + "mesh" : 17, + "name" : "R5_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 20, + 21, + 22 + ], + "name" : "R5_SharedNormals", + "translation" : [ + 0, + -3.1500000953674316, + 0 + ] + }, + { + "mesh" : 18, + "name" : "X2_Label_CoatingOnly", + "translation" : [ + 2.0712804794311523, + 6.619500160217285, + 0 + ] + }, + { + "mesh" : 19, + "name" : "Y0_Label_SimpleCoating", + "translation" : [ + -5.3578033447265625, + 5.25, + 0 + ] + }, + { + "mesh" : 20, + "name" : "Y1_Label_PartialCoating", + "translation" : [ + -5.3578033447265625, + 3.1673200130462646, + 0 + ] + }, + { + "mesh" : 21, + "name" : "Y2_Label_Roughness", + "translation" : [ + -5.3578033447265625, + 1.1383899450302124, + 0 + ] + }, + { + "mesh" : 22, + "name" : "Y3_Label_BaseNormals", + "translation" : [ + -5.3578033447265625, + -1.099429965019226, + 0 + ] + }, + { + "mesh" : 23, + "name" : "Y4_Label_CoatNormals", + "translation" : [ + -5.3578033447265625, + -5.252500057220459, + 0 + ] + }, + { + "mesh" : 24, + "name" : "Y5_Label_SharedNormals", + "translation" : [ + -5.3578033447265625, + -3.1963000297546387, + 0 + ] + }, + { + "mesh" : 25, + "name" : "X0_Label_BaseLayer", + "translation" : [ + -2.087031602859497, + 6.616230010986328, + 0 + ] + }, + { + "mesh" : 26, + "name" : "X1_Label_Coated", + "translation" : [ + 0, + 6.614150047302246, + 0 + ] + } + ], + "materials" : [ + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatTexture": { + "index": 0, + "texCoord": 0 + } + } + } + }, + { + "alphaMode" : "BLEND", + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coating", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1, + "texCoord" : 0 + }, + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 1, + "clearcoatRoughnessTexture": { + "index": 2, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "metallicRoughnessTexture" : { + "index" : 2, + "texCoord" : 0 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Base", + "normalTexture" : { + "index" : 3, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coated", + "normalTexture" : { + "index" : 4, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012848637998104095, + 0.021861059591174126, + 0.11068868637084961, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 5, + "texCoord": 0, + "scale": 1 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coating", + "normalTexture" : { + "index" : 5, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Base", + "normalTexture" : { + "index" : 6, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coated", + "normalTexture" : { + "index" : 7, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 7, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coating", + "normalTexture" : { + "index" : 8, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 1, + 1, + 1 + ], + "emissiveTexture" : { + "index" : 9, + "texCoord" : 0 + }, + "name" : "LabelMaterial", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.8999999761581421 + } + } + ], + "meshes" : [ + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 1 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 2 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 3 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 4 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 5 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 6 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 7 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 8 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 9 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 10 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 11 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 12 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 13 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 14 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 15 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 16 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 17 + } + ] + }, + { + "name" : "Labels_Mesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 8, + "NORMAL" : 9, + "TEXCOORD_0" : 10 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.002", + "primitives" : [ + { + "attributes" : { + "POSITION" : 11, + "NORMAL" : 12, + "TEXCOORD_0" : 13 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 15, + "NORMAL" : 16, + "TEXCOORD_0" : 17 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.004", + "primitives" : [ + { + "attributes" : { + "POSITION" : 18, + "NORMAL" : 19, + "TEXCOORD_0" : 20 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.005", + "primitives" : [ + { + "attributes" : { + "POSITION" : 21, + "NORMAL" : 22, + "TEXCOORD_0" : 23 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.006", + "primitives" : [ + { + "attributes" : { + "POSITION" : 24, + "NORMAL" : 25, + "TEXCOORD_0" : 26 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.007", + "primitives" : [ + { + "attributes" : { + "POSITION" : 27, + "NORMAL" : 28, + "TEXCOORD_0" : 29 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.008", + "primitives" : [ + { + "attributes" : { + "POSITION" : 30, + "NORMAL" : 31, + "TEXCOORD_0" : 32 + }, + "indices" : 7, + "material" : 18 + } + ] + } + ], + "textures" : [ + { + "source" : 0 + }, + { + "source" : 1 + }, + { + "source" : 2 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 4 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 5 + } + ], + "images" : [ + { + "mimeType" : "image/png", + "name" : "PartialCoating", + "uri" : "PartialCoating.png" + }, + { + "mimeType" : "image/png", + "name" : "PartialCoating_Alpha", + "uri" : "PartialCoating_Alpha.png" + }, + { + "mimeType" : "image/png", + "name" : "RoughnessStripes", + "uri" : "RoughnessStripes.png" + }, + { + "mimeType" : "image/png", + "name" : "RibsNormal", + "uri" : "RibsNormal.png" + }, + { + "mimeType" : "image/jpeg", + "name" : "PlasticWrap_normals", + "uri" : "PlasticWrap_normals.jpg" + }, + { + "mimeType" : "image/png", + "name" : "ClearCoatLabels", + "uri" : "ClearCoatLabels.png" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 1113, + "max" : [ + 1, + 1, + 1.0499999523162842 + ], + "min" : [ + -1, + -1, + -0.06000000983476639 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 6180, + "type" : "SCALAR" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 8, + "max" : [ + 1.0280373096466064, + 0.23501670360565186, + 3.8289083903464416e-08 + ], + "min" : [ + -0.968224287033081, + -0.2350165843963623, + -0.010000125505030155 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 7, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.23026323318481445, + 3.751463495405005e-08 + ], + "min" : [ + -2, + -0.23026317358016968, + -0.010000579059123993 + ], + "type" : "VEC3" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 11, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.2302631139755249, + 3.7514624295909016e-08 + ], + "min" : [ + -2, + -0.23026323318481445, + -0.010000428184866905 + ], + "type" : "VEC3" + }, + { + "bufferView" : 12, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 13, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 14, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 15, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22039484977722168, + 3.590687924770464e-08 + ], + "min" : [ + -2, + -0.22039473056793213, + -0.010000280104577541 + ], + "type" : "VEC3" + }, + { + "bufferView" : 16, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.21764808893203735, + 3.545937588000925e-08 + ], + "min" : [ + -2, + -0.21764802932739258, + -0.010000137612223625 + ], + "type" : "VEC3" + }, + { + "bufferView" : 19, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 20, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.20775499939918518, + 3.3847587843638394e-08 + ], + "min" : [ + -2, + -0.20775499939918518, + -0.009999996051192284 + ], + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 23, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 24, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22341907024383545, + 3.6399587344249085e-08 + ], + "min" : [ + -2, + -0.22341907024383545, + -0.009999859146773815 + ], + "type" : "VEC3" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 27, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.9169960618019104, + 0.22670458257198334, + 3.69348676088066e-08 + ], + "min" : [ + -0.9199233651161194, + -0.22670456767082214, + -0.010000176727771759 + ], + "type" : "VEC3" + }, + { + "bufferView" : 28, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.8968609571456909, + 0.20853587985038757, + 3.397480696776256e-08 + ], + "min" : [ + -0.9147982001304626, + -0.2085357904434204, + -0.010000113397836685 + ], + "type" : "VEC3" + }, + { + "bufferView" : 31, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 32, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 13356 + }, + { + "buffer" : 0, + "byteLength" : 8904, + "byteOffset" : 26712 + }, + { + "buffer" : 0, + "byteLength" : 12360, + "byteOffset" : 35616 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 47976 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48072 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48168 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48232 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48256 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48352 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48448 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48512 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48608 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48704 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48768 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48792 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48888 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48984 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49048 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49144 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49240 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49304 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49400 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49496 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49560 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49656 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49752 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49816 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49912 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50008 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50072 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50168 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50264 + } + ], + "buffers" : [ + { + "byteLength" : 50328, + "uri" : "ClearCoatTest.bin" + } + ] +} diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating.png new file mode 100644 index 0000000000000000000000000000000000000000..d579bbebb8a423eedb16594a7ef715fe7521a752 GIT binary patch literal 5077 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 Q@JSA$qoJ>ky=fQyKcnMWr2qf` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..b1911d9f0a303a57181d44856f3f31ea18c11063 GIT binary patch literal 5065 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 Q_!|ylXYGipvh+*(4_Os-!2kdN literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg b/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73e6260ef7126ccb11113179c58cf6611d3f3e9d GIT binary patch literal 144210 zcmbTddpy&9{QtkM4mwFHL}gd0Tv3)p6n2#džOVOJDoNkR^>T_xnWONE@;Rbq*4 zIn0@rb2*>3a?BiOIgBl{!+XD%>+?JP@%`ib`wh3fUNvRhT{&|%F#j%c0KH#lWzblU9v1#^pwmR5Gx>>be89Wm}V zZ+Up$zT@TRAMh|RC^#hiX+&hyv*?)TFJ2}kr@Tr{dz!pljPw6`{4d9)2*&l> zx^-*U$*qoS&2NF=XRYG8_4{=<>^OTx?uPHq11BDD+;uMTO~L0)8hTe3l-wS4ZB{;b za`X^yb!h*M?0;`yPyT<6?EfCv|1++B*w(dcz~rq}gdt&#orEzV+B#P%5TW(|mBE@8 zS=JvGIDFd7SL`O?FBfPEX_wMR7@O`>;h^bu&`Yma)=B7aXdi*y(i>1J>Z1 zz1CTd`n94v@k@%hY{oRAvM!yOmqt`BfRxOCOs>p8Il6b?qs|=`>mHp9NH~CHPDbQ; z^oD7};Qa5Vbo$NjC2*@cyYwj$^JG%CMwAih&>(lyF~Q^4J92YNnA906$eL`kJ?7NQ}nL{3T+omFSO1Ea7EQ zTxSKZ_o0e>q!F98vd4Y}Z_XsjVCpFQd==<$452>{V<>~2^J#qB!-5X~DQ4yFd#eRL zee{YtiA-pEFG8mWeJ;wShuB0ZXK`FT*>#y$7;1}uAVP9Qo_nmJ9n&W9 z%!KlIG!>J9Ipm+Chwh$Od4VOo_sRCdsGROWTfQf}_nP$IDTD1KQ$tIoUujv&Au`yg z_2#Os@77RmP0}QEXm@!CKh-F{CTU7t6SNJ(xF_%PXyRhW>>q?OJe}z*9d#x>hDJp! zC*R&{li2yZPVQ*O%Fa&{l8X-unY5?d{?aG1!y~a2FX8dY4K#~gj8P*ITt6~X7};cb z$PY6$qSSpcnCjz<)DNArk@(FmO+d{zhKc+en0`%32(7!2T25EWd&|4auoC^mE|_}O zO;~+vk-_{siFsk#&v-s|0_ud^Fm+EuCW={JIL~gLW)oIaq!Zp7*?RnZAH@<+ipwsx z-u3<^4?ovFmsH^|PlOQlM0X_ne8@H8vX_B=BQZL9+Eot8Lr~{!!5Z~*R(XOPt7iq@ zXeg(h(Q};|Bv{^mA%oq@hIY1A-Pe1h$$URRlUR2b8{=d!cwv}Vc7+U9Uyd8j>gj^N zV8$FRT5q+iu@!XA;r8RJ;G4EC_w0f5Gvz61e`AsA6+!kBLfKtWTmx1~Y5dOTT4vz0vYu>_Lx!1B3foKu+rhynMZ%q} z1SUtDhCW@B!L}I3_y{@#l!3KS_`bhdAUZ^^{EN`}ye5WJ-!aj;NBXllz*qcOkiQ9~ z-|3Yl-OVzU6y%Z`Z^dA14)vs~z~e&>zc_r zt70#$&;vxQ!1umnQuh5ccv~KgSy_#WUCXB-R3#W(t5hyRMXY?-m8|&zE=-0~?vGD` zMaQ~^`-^Am6(+Jhg zP~0eXW#ZA@U(V`qK~l%`F%Y}jz4SuaR?`b6~~l7y`jt`!3FzW4P9%7`&Y-nERpCt&zbJ9|=GRiZ(OYB%peJ))q5GEEI3AM+2VT9(2^r%(Z>3wB0nS-s6MBo?zDb zFY-9mq6t?K#LfF#!gcP5PdV|1dUr>O#&{TZr)g-3b=%&C&kc!6af6AN0Uh{z6V~;pYpC zPcf_NZkKqzS*ZI?@E*1?SPBO63urDg7O z>xgW#FL()mE@@w;+~P8LGdP=V=!N{YK*+?#w@cW?|27eNawM^}zvPgTMLk*X7$&0N zcTpd5`_ch)fX`D2VISg`LsgeTWV?QlDV_UO2Gc?lg`s2iQC!!eJ>%xu?Rk3|5xpW? zC6dmL4@>5UKj^(Tx%rk*|8>orw)<7pUiBh(lrU|8_$wlyZq#tbEFA<-ABg;}UROk6)ZhT~SS>VDG9MhJlyrJh4{MWLdDWHNJb7i3Dpk

8#fXvU&U@TnHe66M)}` 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≫PJyOL#2noO42?ak;#zU>=*qI;o{}k?8jYM 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�-h@x9GW3t>NHF#%4~dx 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 literal 0 HcmV?d00001 diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 766372325..ee8b1a742 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -151,8 +151,65 @@ TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); } +void VerifyClearCoatScene(const aiScene *scene) { + ASSERT_NE(nullptr, scene); + + ASSERT_TRUE(scene->HasMaterials()); + + // Find a specific Clearcoat material and check the values + const aiString partial_coated("Partial_Coated"); + bool found_partial_coat = false; + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + const aiMaterial *material = scene->mMaterials[i]; + ASSERT_NE(nullptr, material); + if (material->GetName() == partial_coated) { + found_partial_coat = true; + + ai_real clearcoat_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat_factor)); + EXPECT_EQ(ai_real(1.0f), clearcoat_factor); + + ai_real clearcoat_rough_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat_rough_factor)); + EXPECT_EQ(ai_real(0.03f), clearcoat_rough_factor); + + // Should import the texture as diffuse and as base color + aiString path; + std::array modes; + static const std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_CLEARCOAT_TEXTURE, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "PartialCoating.png"); + EXPECT_EQ(exp_modes, modes); + } + } + EXPECT_TRUE(found_partial_coat); +} + +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_clearcoat) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + #ifndef ASSIMP_BUILD_NO_EXPORT +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_clearcoat) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb")); + } + + // And re-import + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossiness) { Assimp::Importer importer; Assimp::Exporter exporter; From 230f367ef92c9b9d187a64bf8308e6a56b12ade1 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:17:25 +0100 Subject: [PATCH 14/44] Fix glTFv2 texcoord/uv mapping Use the standard property to indicate the UV map index --- code/AssetLib/glTF2/glTF2Exporter.cpp | 21 ++++++----- code/AssetLib/glTF2/glTF2Exporter.h | 2 +- code/AssetLib/glTF2/glTF2Importer.cpp | 3 +- include/assimp/material.h | 4 +-- include/assimp/pbrmaterial.h | 4 +-- test/unit/utglTF2ImportExport.cpp | 50 ++++++++++++++++++++------- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 3cb3891b0..7e0966aff 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -492,11 +492,14 @@ void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, float& prop, const char mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot = 0) { if (mat.GetTextureCount(tt) > 0) { aiString tex; + // Read texcoord (UV map index) + mat.Get(AI_MATKEY_UVWSRC(tt, slot), texCoord); + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); @@ -572,21 +575,21 @@ void glTF2Exporter::GetMatTex(const aiMaterial& mat, TextureInfo& prop, aiTextur { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + GetMatTex(mat, texture, prop.texCoord, tt, slot); - if (texture) { - GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); - } + //if (texture) { + // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + //} } void glTF2Exporter::GetMatTex(const aiMaterial& mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + 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); } } @@ -595,10 +598,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial& mat, OcclusionTextureInfo& prop, { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + 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); } } diff --git a/code/AssetLib/glTF2/glTF2Exporter.h b/code/AssetLib/glTF2/glTF2Exporter.h index edc85d998..f5238297f 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.h +++ b/code/AssetLib/glTF2/glTF2Exporter.h @@ -104,7 +104,7 @@ namespace Assimp void GetTexSampler(const aiMaterial& mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); void GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); void GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b435f111d..aadc9fb16 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -165,7 +165,8 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); - mat->AddProperty(&prop.texCoord, 1, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(texType, texSlot)); + const int uvIndex = static_cast(prop.texCoord); + mat->AddProperty(&uvIndex, 1, AI_MATKEY_UVWSRC(texType, texSlot)); if (prop.textureTransformSupported) { aiUVTransform transform; diff --git a/include/assimp/material.h b/include/assimp/material.h index f348da369..2024be07f 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -144,9 +144,7 @@ enum aiTextureMapMode { enum aiTextureMapping { /** The mapping coordinates are taken from an UV channel. * - * #AI_MATKEY_UVWSRC property - * - * Specifies from which UV channel + * #AI_MATKEY_UVWSRC property specifies from which UV channel * the texture coordinates are to be taken from (remember, * meshes can have more than one UV channel). */ diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index c67bcc3b8..93e7e3095 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -75,7 +75,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 //#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 -#define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" +//#define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" #define _AI_MATKEY_GLTF_MAPPINGNAME_BASE "$tex.mappingname" #define _AI_MATKEY_GLTF_MAPPINGID_BASE "$tex.mappingid" #define _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE "$tex.mappingfiltermag" @@ -83,7 +83,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define _AI_MATKEY_GLTF_SCALE_BASE "$tex.scale" #define _AI_MATKEY_GLTF_STRENGTH_BASE "$tex.strength" -#define AI_MATKEY_GLTF_TEXTURE_TEXCOORD(type, N) _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, type, N +//#define AI_MATKEY_GLTF_TEXTURE_TEXCOORD(type, N) _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGNAME(type, N) _AI_MATKEY_GLTF_MAPPINGNAME_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGID(type, N) _AI_MATKEY_GLTF_MAPPINGID_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGFILTER_MAG(type, N) _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE, type, N diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index ee8b1a742..2c000bb37 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -603,32 +603,58 @@ TEST_F(utglTF2ImportExport, sceneMetadata) { } TEST_F(utglTF2ImportExport, texcoords) { + Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_TRUE(scene->HasMaterials()); + const aiMaterial *material = scene->mMaterials[0]; + + aiString path; + unsigned int uvIndex = 255; + aiTextureMapMode modes[2]; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 0); + + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 1); +} + +#ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, texcoords_export) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf_out.glb")); + } + + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); ASSERT_NE(scene, nullptr); ASSERT_TRUE(scene->HasMaterials()); const aiMaterial *material = scene->mMaterials[0]; aiString path; + unsigned int uvIndex = 255; aiTextureMapMode modes[2]; - EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); EXPECT_STREQ(path.C_Str(), "texture.png"); - - int uvIndex = -1; - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_DIFFUSE, 0), &uvIndex), aiReturn_SUCCESS); EXPECT_EQ(uvIndex, 0); - // Using manual macro expansion of AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE here. - // The following works with some but not all compilers: - // #define APPLY(X, Y) X(Y) - // ..., APPLY(AI_MATKEY_GLTF_TEXTURE_TEXCOORD, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE), ... - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_UNKNOWN, 0), &uvIndex), aiReturn_SUCCESS); + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); EXPECT_EQ(uvIndex, 1); } +#endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, recursive_nodes) { Assimp::Importer importer; const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/RecursiveNodes/RecursiveNodes.gltf", aiProcess_ValidateDataStructure); From fbf75963dc73c4bb70001f358989ed8ebb0ce6ac Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:25:02 +0100 Subject: [PATCH 15/44] Fix typo Thank you clang! --- code/AssetLib/glTF2/glTF2Exporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 7e0966aff..40ba41c20 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -655,7 +655,7 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo // Add any appropriate textures GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - result == result || pbrSG.specularGlossinessTexture.texture; + result = result || pbrSG.specularGlossinessTexture.texture; if (result) { // Likely to always have diffuse From 36c8cdf3dee0f846eef2bd82e8564925ef2077bc Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 12 Jun 2021 11:44:28 +1000 Subject: [PATCH 16/44] Add scene metadata for glTF2 files as allowed by the glTF2 specification. --- code/AssetLib/glTF2/glTF2Asset.h | 2 ++ code/AssetLib/glTF2/glTF2Asset.inl | 3 +++ code/AssetLib/glTF2/glTF2Importer.cpp | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 6aa0f92ed..b1fba7bd5 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -922,6 +922,8 @@ struct Scene : public Object { std::string name; std::vector> nodes; + CustomExtension extensions; + Scene() {} void Read(Value &obj, Asset &r); }; diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index c4b1e72ee..14088353c 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1803,6 +1803,9 @@ inline void Scene::Read(Value &obj, Asset &r) { this->nodes.push_back(node); } } + if (Value *extensions = FindObject(obj, "extensions")) { + this->extensions = ReadExtensions("extensions", *extensions); + } } inline void Skin::Read(Value &obj, Asset &r) { diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index c62989c3b..9ba6270ed 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -1498,7 +1498,8 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { const bool hasVersion = !a.asset.version.empty(); const bool hasGenerator = !a.asset.generator.empty(); const bool hasCopyright = !a.asset.copyright.empty(); - if (hasVersion || hasGenerator || hasCopyright) { + const bool hasSceneMetadata = a.scene->extensions; + if (hasVersion || hasGenerator || hasCopyright || hasSceneMetadata) { mScene->mMetaData = new aiMetadata; if (hasVersion) { mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version)); @@ -1509,6 +1510,9 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { if (hasCopyright) { mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); } + if (hasSceneMetadata) { + ParseExtensions(mScene->mMetaData, a.scene->extensions); + } } } From 5be2330fbb969e8e8f669fc2f225cd6cb594674c Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 12 Jun 2021 12:20:40 +1000 Subject: [PATCH 17/44] Added CustomExtension to glTF2::Object so that all subclasses have it instead of doing it piecemeal. --- code/AssetLib/glTF2/glTF2Asset.h | 95 ++++++++++++++++---------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index b1fba7bd5..4aec48586 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -356,6 +356,51 @@ struct Nullable { isPresent(true) {} }; +struct CustomExtension { + // + // A struct containing custom extension data added to a glTF2 file + // Has to contain Object, Array, String, Double, Uint64, and Int64 at a minimum + // String, Double, Uint64, and Int64 are stored in the Nullables + // Object and Array are stored in the std::vector + // + std::string name; + + Nullable mStringValue; + Nullable mDoubleValue; + Nullable mUint64Value; + Nullable mInt64Value; + Nullable mBoolValue; + + // std::vector handles both Object and Array + Nullable> mValues; + + operator bool() const { + return Size() != 0; + } + + size_t Size() const { + if (mValues.isPresent) { + return mValues.value.size(); + } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { + return 1; + } + return 0; + } + + 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) + { + } +}; + //! Base class for all glTF top-level objects struct Object { int index; //!< The index of this object within its property container @@ -363,6 +408,8 @@ struct Object { std::string id; //!< The globally unique ID used to reference this object std::string name; //!< The user-defined name of this object + CustomExtension extensions; + //! Objects marked as special are not exported (used to emulate the binary body buffer) virtual bool IsSpecial() const { return false; } @@ -834,50 +881,6 @@ struct Mesh : public Object { void Read(Value &pJSON_Object, Asset &pAsset_Root); }; -struct CustomExtension : public Object { - // - // A struct containing custom extension data added to a glTF2 file - // Has to contain Object, Array, String, Double, Uint64, and Int64 at a minimum - // String, Double, Uint64, and Int64 are stored in the Nullables - // Object and Array are stored in the std::vector - // - - Nullable mStringValue; - Nullable mDoubleValue; - Nullable mUint64Value; - Nullable mInt64Value; - Nullable mBoolValue; - - // std::vector handles both Object and Array - Nullable> mValues; - - operator bool() const { - return Size() != 0; - } - - size_t Size() const { - if (mValues.isPresent) { - return mValues.value.size(); - } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { - return 1; - } - return 0; - } - - CustomExtension() = default; - - CustomExtension(const CustomExtension &other) - : Object(other) - , mStringValue(other.mStringValue) - , mDoubleValue(other.mDoubleValue) - , mUint64Value(other.mUint64Value) - , mInt64Value(other.mInt64Value) - , mBoolValue(other.mBoolValue) - , mValues(other.mValues) - { - } -}; - struct Node : public Object { std::vector> children; std::vector> meshes; @@ -896,8 +899,6 @@ struct Node : public Object { Ref parent; //!< This is not part of the glTF specification. Used as a helper. - CustomExtension extensions; - Node() {} void Read(Value &obj, Asset &r); }; @@ -922,8 +923,6 @@ struct Scene : public Object { std::string name; std::vector> nodes; - CustomExtension extensions; - Scene() {} void Read(Value &obj, Asset &r); }; From 7f0efa0866920ec15dd5e268befb67125ff7f6c0 Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 12 Jun 2021 12:50:44 +1000 Subject: [PATCH 18/44] Added ReadExtensions to glTF2::Object, so all objects now have their extensions read. Importer is the only place that needs to be modified to make them available on the Assimp side now. --- code/AssetLib/glTF2/glTF2Asset.h | 2 + code/AssetLib/glTF2/glTF2Asset.inl | 85 ++++++++++++++++-------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 4aec48586..759411b13 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -424,6 +424,8 @@ struct Object { inline Value *FindArray(Value &val, const char *id); inline Value *FindObject(Value &val, const char *id); inline Value *FindExtension(Value &val, const char *extensionId); + + inline void ReadExtensions(Value &val); }; // diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 135def597..24fc48b3e 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -304,6 +304,43 @@ inline Value *FindObject(Document &doc, const char *memberId) { inline Value *FindExtension(Value &val, const char *extensionId) { return FindExtensionInContext(val, extensionId, "the document"); } + +inline CustomExtension ReadExtensions(const char *name, Value &obj) { + CustomExtension ret; + ret.name = name; + if (obj.IsObject()) { + ret.mValues.isPresent = true; + for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { + auto &val = it->value; + ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); + } + } else if (obj.IsArray()) { + ret.mValues.value.reserve(obj.Size()); + ret.mValues.isPresent = true; + for (unsigned int i = 0; i < obj.Size(); ++i) { + ret.mValues.value.push_back(ReadExtensions(name, obj[i])); + } + } else if (obj.IsNumber()) { + if (obj.IsUint64()) { + ret.mUint64Value.value = obj.GetUint64(); + ret.mUint64Value.isPresent = true; + } else if (obj.IsInt64()) { + ret.mInt64Value.value = obj.GetInt64(); + ret.mInt64Value.isPresent = true; + } else if (obj.IsDouble()) { + ret.mDoubleValue.value = obj.GetDouble(); + ret.mDoubleValue.isPresent = true; + } + } else if (obj.IsString()) { + ReadValue(obj, ret.mStringValue); + ret.mStringValue.isPresent = true; + } else if (obj.IsBool()) { + ret.mBoolValue.value = obj.GetBool(); + ret.mBoolValue.isPresent = true; + } + return ret; +} + } // namespace inline Value *Object::FindString(Value &val, const char *memberId) { @@ -330,6 +367,12 @@ inline Value *Object::FindExtension(Value &val, const char *extensionId) { return FindExtensionInContext(val, extensionId, id.c_str(), name.c_str()); } +inline void Object::ReadExtensions(Value &val) { + if (Value *extensions = FindObject(val, "extensions")) { + this->extensions = glTF2::ReadExtensions("extensions", *extensions); + } +} + #ifdef ASSIMP_ENABLE_DRACO template @@ -569,6 +612,7 @@ Ref LazyDict::Retrieve(unsigned int i) { inst->oIndex = i; ReadMember(obj, "name", inst->name); inst->Read(obj, mAsset); + inst->ReadExtensions(obj); Ref result = Add(inst.release()); mRecursiveReferenceCheck.erase(i); @@ -1683,42 +1727,6 @@ inline void Light::Read(Value &obj, Asset & /*r*/) { } } -inline CustomExtension ReadExtensions(const char *name, Value &obj) { - CustomExtension ret; - ret.name = name; - if (obj.IsObject()) { - ret.mValues.isPresent = true; - for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { - auto &val = it->value; - ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); - } - } else if (obj.IsArray()) { - ret.mValues.value.reserve(obj.Size()); - ret.mValues.isPresent = true; - for (unsigned int i = 0; i < obj.Size(); ++i) { - ret.mValues.value.push_back(ReadExtensions(name, obj[i])); - } - } else if (obj.IsNumber()) { - if (obj.IsUint64()) { - ret.mUint64Value.value = obj.GetUint64(); - ret.mUint64Value.isPresent = true; - } else if (obj.IsInt64()) { - ret.mInt64Value.value = obj.GetInt64(); - ret.mInt64Value.isPresent = true; - } else if (obj.IsDouble()) { - ret.mDoubleValue.value = obj.GetDouble(); - ret.mDoubleValue.isPresent = true; - } - } else if (obj.IsString()) { - ReadValue(obj, ret.mStringValue); - ret.mStringValue.isPresent = true; - } else if (obj.IsBool()) { - ret.mBoolValue.value = obj.GetBool(); - ret.mBoolValue.isPresent = true; - } - return ret; -} - inline void Node::Read(Value &obj, Asset &r) { if (name.empty()) { name = id; @@ -1775,8 +1783,6 @@ inline void Node::Read(Value &obj, Asset &r) { Value *curExtensions = FindObject(obj, "extensions"); if (nullptr != curExtensions) { - this->extensions = ReadExtensions("extensions", *curExtensions); - if (r.extensionsUsed.KHR_lights_punctual) { if (Value *ext = FindObject(*curExtensions, "KHR_lights_punctual")) { Value *curLight = FindUInt(*ext, "light"); @@ -1805,9 +1811,6 @@ inline void Scene::Read(Value &obj, Asset &r) { this->nodes.push_back(node); } } - if (Value *extensions = FindObject(obj, "extensions")) { - this->extensions = ReadExtensions("extensions", *extensions); - } } inline void Skin::Read(Value &obj, Asset &r) { From a7a30baf27fd0e9dac8e512aff1c4a37cefa93eb Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 12 Jun 2021 13:08:14 +1000 Subject: [PATCH 19/44] Renamed local variable to avoid shadowing member variable. --- code/AssetLib/glTF2/glTF2Asset.inl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 24fc48b3e..8175ab428 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -368,8 +368,8 @@ inline Value *Object::FindExtension(Value &val, const char *extensionId) { } inline void Object::ReadExtensions(Value &val) { - if (Value *extensions = FindObject(val, "extensions")) { - this->extensions = glTF2::ReadExtensions("extensions", *extensions); + if (Value *curExtensions = FindObject(val, "extensions")) { + this->extensions = glTF2::ReadExtensions("extensions", *curExtensions); } } From 3de20af3cc214c2459bce5013be61518ef49d7fc Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 12 Jun 2021 13:16:53 +1000 Subject: [PATCH 20/44] Renamed glTF2::Object::extensions to customExtensions to avoid shadowing in other subclasses. --- code/AssetLib/glTF2/glTF2Asset.h | 2 +- code/AssetLib/glTF2/glTF2Asset.inl | 2 +- code/AssetLib/glTF2/glTF2Importer.cpp | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 759411b13..25e917712 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -408,7 +408,7 @@ struct Object { std::string id; //!< The globally unique ID used to reference this object std::string name; //!< The user-defined name of this object - CustomExtension extensions; + CustomExtension customExtensions; //! Objects marked as special are not exported (used to emulate the binary body buffer) virtual bool IsSpecial() const { return false; } diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 8175ab428..f88ea359d 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -369,7 +369,7 @@ inline Value *Object::FindExtension(Value &val, const char *extensionId) { inline void Object::ReadExtensions(Value &val) { if (Value *curExtensions = FindObject(val, "extensions")) { - this->extensions = glTF2::ReadExtensions("extensions", *curExtensions); + this->customExtensions = glTF2::ReadExtensions("extensions", *curExtensions); } } diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 9ba6270ed..8ccc6b058 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -489,7 +489,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { "\" does not match the vertex count"); continue; } - + auto componentType = attr.color[c]->componentType; if (componentType == glTF2::ComponentType_FLOAT) { attr.color[c]->ExtractData(aim->mColors[c]); @@ -1002,9 +1002,9 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & } } - if (node.extensions) { + if (node.customExtensions) { ainode->mMetaData = new aiMetadata; - ParseExtensions(ainode->mMetaData, node.extensions); + ParseExtensions(ainode->mMetaData, node.customExtensions); } GetNodeTransform(ainode->mTransformation, node); @@ -1498,7 +1498,7 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { const bool hasVersion = !a.asset.version.empty(); const bool hasGenerator = !a.asset.generator.empty(); const bool hasCopyright = !a.asset.copyright.empty(); - const bool hasSceneMetadata = a.scene->extensions; + const bool hasSceneMetadata = a.scene->customExtensions; if (hasVersion || hasGenerator || hasCopyright || hasSceneMetadata) { mScene->mMetaData = new aiMetadata; if (hasVersion) { @@ -1511,7 +1511,7 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); } if (hasSceneMetadata) { - ParseExtensions(mScene->mMetaData, a.scene->extensions); + ParseExtensions(mScene->mMetaData, a.scene->customExtensions); } } } From 064ffc625bef301a03a91a0e3ab7104466101605 Mon Sep 17 00:00:00 2001 From: Evangel Date: Mon, 14 Jun 2021 12:21:29 +1000 Subject: [PATCH 21/44] Const qualify aiMetadata::HasKey --- include/assimp/metadata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/assimp/metadata.h b/include/assimp/metadata.h index 551a9aba4..57fedfbfc 100644 --- a/include/assimp/metadata.h +++ b/include/assimp/metadata.h @@ -432,7 +432,7 @@ struct aiMetadata { /// Check whether there is a metadata entry for the given key. /// \param [in] Key - the key value value to check for. - inline bool HasKey(const char *key) { + inline bool HasKey(const char *key) const { if (nullptr == key) { return false; } From 44763528829fec49c46b6462df0d42a6451191e1 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:13:46 +0100 Subject: [PATCH 22/44] First pass at simplifying glTFv2 PBR Removed 'core' set of GLTF-specific properties --- code/AssetLib/glTF2/glTF2Exporter.cpp | 60 ++++++++++++++------------- code/AssetLib/glTF2/glTF2Importer.cpp | 21 ++++++---- include/assimp/material.h | 40 +++++++++++++++++- include/assimp/pbrmaterial.h | 14 +++---- test/unit/utglTF2ImportExport.cpp | 55 ++++++++++++++++++++---- 5 files changed, 138 insertions(+), 52 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 751508225..dfa62372a 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -645,7 +645,7 @@ void glTF2Exporter::ExportMaterials() m->name = name; - GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + 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 @@ -654,19 +654,19 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR) != AI_SUCCESS) { + 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 GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + 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 m->pbrMetallicRoughness.metallicFactor = 0; } // get roughness if source is gltf2 file - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { // otherwise, try to derive and convert from specular + shininess values aiColor4D specularColor; ai_real shininess; @@ -712,36 +712,38 @@ void glTF2Exporter::ExportMaterials() } } - bool hasPbrSpecularGlossiness = false; - mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS, hasPbrSpecularGlossiness); - - if (hasPbrSpecularGlossiness) { - - if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { - mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; - } - + { + // If has a Specular color, use the KHR_materials_pbrSpecularGlossiness extension PbrSpecularGlossiness pbrSG; - - GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); - GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR); - - if (mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { - float shininess; - - if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = shininess / 1000; + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; } + + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + + // If don't have explicit glossiness then convert from roughness or shininess + if (mat->Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { + float shininess; + if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + // Add any appropriate textures + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + m->pbrSpecularGlossiness = Nullable(pbrSG); } - - GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); - GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - - m->pbrSpecularGlossiness = Nullable(pbrSG); } - bool unlit; - if (mat->Get(AI_MATKEY_GLTF_UNLIT, unlit) == AI_SUCCESS && unlit) { + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; + mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode); + if (shadingMode == aiShadingMode_Unlit) { mAsset->extensionsUsed.KHR_materials_unlit = true; m->unlit = true; } diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index c62989c3b..b5ba65857 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -238,16 +238,18 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&str, AI_MATKEY_NAME); } + // Set Assimp DIFFUSE and BASE COLOR to the pbrMetallicRoughness base color and texture for backwards compatibility + // Technically should not load any pbrMetallicRoughness if extensionsRequired contains KHR_materials_pbrSpecularGlossiness SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); - aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_ROUGHNESS_FACTOR); float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; roughnessAsShininess *= roughnessAsShininess * 1000; @@ -268,22 +270,27 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M if (mat.pbrSpecularGlossiness.isPresent) { PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; - aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS); SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); - aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLOSSINESS_FACTOR); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); } + + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; if (mat.unlit) { - aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); + shadingMode = aiShadingMode_Unlit; } + aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); + + //KHR_materials_sheen if (mat.materialSheen.isPresent) { MaterialSheen &sheen = mat.materialSheen.value; diff --git a/include/assimp/material.h b/include/assimp/material.h index 08c0491c0..11cdef1f4 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -202,11 +202,15 @@ enum aiTextureType { /** The texture is combined with the result of the diffuse * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_DIFFUSE = 1, /** The texture is combined with the result of the specular * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_SPECULAR = 2, @@ -309,7 +313,9 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); // --------------------------------------------------------------------------- /** @brief Defines all shading models supported by the library - * + * + * #AI_MATKEY_SHADING_MODEL + * * The list of shading modes has been taken from Blender. * See Blender documentation for more information. The API does * not distinguish between "specular" and "diffuse" shaders (thus the @@ -364,13 +370,27 @@ enum aiShadingMode { aiShadingMode_CookTorrance = 0x8, /** No shading at all. Constant light influence of 1.0. + * Also known as "Unlit" */ aiShadingMode_NoShading = 0x9, + aiShadingMode_Unlit = aiShadingMode_NoShading, // Alias /** Fresnel shading */ aiShadingMode_Fresnel = 0xa, + /** Physically-Based Rendering (PBR) shading using + * Bidirectional scattering/reflectance distribution function (BSDF/BRDF) + * There are multiple methods under this banner, and model files may provide + * data for more than one PBR-BRDF method. + * Applications should use the set of provided properties to determine which + * of their preferred PBDR methods are available + * eg: + * - If AI_MATKEY_METALLIC_FACTOR is set, then a Metallic/Roughness is available + * - If AI_MATKEY_COLOR_SPECULAR is set, then a Specular/Glossiness is available + */ + aiShadingMode_PBR_BRDF = 0xb, + #ifndef SWIG _aiShadingMode_Force32Bit = INT_MAX #endif @@ -923,11 +943,29 @@ extern "C" { // --------------------------------------------------------------------------- // PBR material support #define AI_MATKEY_USE_COLOR_MAP "$mat.useColorMap", 0, 0 + +// Metallic/Roughness Workflow +// --------------------------- +// Base color factor. Will be multiplied by final base color texture values if extant #define AI_MATKEY_BASE_COLOR "$clr.base", 0, 0 #define AI_MATKEY_USE_METALLIC_MAP "$mat.useMetallicMap", 0, 0 +// Metallic factor. 0.0 = Full Dielectric, 1.0 = Full Metal #define AI_MATKEY_METALLIC_FACTOR "$mat.metallicFactor", 0, 0 #define AI_MATKEY_USE_ROUGHNESS_MAP "$mat.useRoughnessMap", 0, 0 +// Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 + +// Specular/Glossiness Workflow +// --------------------------- +// Diffuse/Albedo Color. Note: Pure Metals have a diffuse of {0,0,0} +// AI_MATKEY_COLOR_DIFFUSE +// Specular Color +// AI_MATKEY_COLOR_SPECULAR +// Glossiness factor. 0.0 = Completely Rough, 1.0 = Perfectly Smooth +#define AI_MATKEY_GLOSSINESS_FACTOR "$mat.glossinessFactor", 0, 0 + +// Emissive +// -------- #define AI_MATKEY_USE_EMISSIVE_MAP "$mat.useEmissiveMap", 0, 0 #define AI_MATKEY_EMISSIVE_INTENSITY "$mat.emissiveIntensity", 0, 0 #define AI_MATKEY_USE_AO_MAP "$mat.useAOMap", 0, 0 diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index 2e41b8b6d..fd904e4fc 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -50,16 +50,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # pragma GCC system_header #endif -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE aiTextureType_DIFFUSE, 1 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE aiTextureType_DIFFUSE, 1 #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 0 #define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0 #define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0 -#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 -#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 -#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 +//#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 +//#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 +//#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 #define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 4110edcfc..e0ac10ad5 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -57,10 +57,9 @@ using namespace Assimp; class utglTF2ImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + virtual bool importerMatTest(const char *file, bool spec_gloss, std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure); EXPECT_NE(scene, nullptr); if (!scene) { return false; @@ -72,13 +71,49 @@ public: } const aiMaterial *material = scene->mMaterials[0]; + // This Material should be a PBR + aiShadingMode shadingMode; + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_SHADING_MODEL, shadingMode)); + EXPECT_EQ(aiShadingMode_PBR_BRDF, shadingMode); + + // Should import the texture as diffuse and as base color aiString path; - aiTextureMapMode modes[2]; + std::array modes; EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + nullptr, nullptr, modes.data())); EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); - EXPECT_EQ(modes[0], aiTextureMapMode_Mirror); - EXPECT_EQ(modes[1], aiTextureMapMode_Clamp); + EXPECT_EQ(exp_modes, modes); + + // Also as Base Color + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_BASE_COLOR, 0, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); + EXPECT_EQ(exp_modes, modes); + + // Should have a MetallicFactor (default is 1.0) + ai_real metal_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_METALLIC_FACTOR, metal_factor)); + EXPECT_EQ(ai_real(0.0), metal_factor); + + // And a roughness factor (default is 1.0) + ai_real roughness_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness_factor)); + EXPECT_EQ(ai_real(1.0), roughness_factor); + + aiColor3D spec_color = { 0, 0, 0 }; + ai_real glossiness = ai_real(0.5); + if (spec_gloss) { + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + constexpr ai_real spec_val(0.20000000298023225); // From the file + EXPECT_EQ(spec_val, spec_color.r); + EXPECT_EQ(spec_val, spec_color.g); + EXPECT_EQ(spec_val, spec_color.b); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + EXPECT_EQ(ai_real(1.0), glossiness); + } else { + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + } return true; } @@ -105,13 +140,17 @@ public: }; TEST_F(utglTF2ImportExport, importglTF2FromFileTest) { - EXPECT_TRUE(importerTest()); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", false, {aiTextureMapMode_Mirror, aiTextureMapMode_Clamp})); } TEST_F(utglTF2ImportExport, importBinaryglTF2FromFileTest) { EXPECT_TRUE(binaryImporterTest()); } +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); +} + #ifndef ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; From 4a66ec25d0371f694e3aa41f3a0996c24a606651 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 13:44:52 +0100 Subject: [PATCH 23/44] Standardise Clearcoat, Sheen and Transmission Also cleanup glTFv2 defaults, don't import/export if disabled --- code/AssetLib/glTF2/glTF2Exporter.cpp | 236 +++++++++++++++----------- code/AssetLib/glTF2/glTF2Exporter.h | 26 ++- code/AssetLib/glTF2/glTF2Importer.cpp | 39 ++--- code/Common/material.cpp | 12 +- include/assimp/material.h | 77 ++++++++- include/assimp/pbrmaterial.h | 28 +-- test/unit/utglTF2ImportExport.cpp | 15 ++ 7 files changed, 281 insertions(+), 152 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index dfa62372a..3cb3891b0 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -436,11 +436,11 @@ inline void SetSamplerWrap(SamplerWrap& wrap, aiTextureMapMode map) }; } -void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetTexSampler(const aiMaterial& mat, Ref texture, aiTextureType tt, unsigned int slot) { aiString aId; std::string id; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { id = aId.C_Str(); } @@ -455,49 +455,49 @@ void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, a SamplerMagFilter filterMag; SamplerMinFilter filterMin; - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapS, mapU); } - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapT, mapV); } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { texture->sampler->magFilter = filterMag; } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { texture->sampler->minFilter = filterMin; } aiString name; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { texture->sampler->name = name.C_Str(); } } } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) { - if (mat->GetTextureCount(tt) > 0) { + if (mat.GetTextureCount(tt) > 0) { aiString tex; - if (mat->Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); if (path.size() > 0) { @@ -568,7 +568,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -579,7 +579,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, TextureInfo& prop, aiTextur } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -591,7 +591,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, NormalTextureInfo& prop, ai } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; @@ -603,10 +603,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, OcclusionTextureInfo& prop, } } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec4& prop, const char* propName, int type, int idx) const { aiColor4D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; prop[3] = col.a; @@ -615,30 +615,109 @@ aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const cha return result; } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec3& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec3& prop, const char* propName, int type, int idx) const { aiColor3D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { - prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; + prop[0] = col.r; + prop[1] = col.g; + prop[2] = col.b; } return result; } +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 { + // Don't have explicit glossiness, convert from pbr roughness or legacy shininess + float shininess; + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + result = true; + } + // Add any appropriate textures + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + result == result || pbrSG.specularGlossinessTexture.texture; + + if (result) { + // Likely to always have diffuse + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + } + + return result; +} + +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) + return false; + + // Default Sheen color factor {0,0,0} disables Sheen, so do not export + if (sheen.sheenColorFactor == defaultSheenFactor) + return false; + + mat.Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); + + GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_SHEEN_COLOR_TEXTURE); + GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat) { + if (mat.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor) != aiReturn_SUCCESS) { + return false; + } + + // Clearcoat factor of zero disables Clearcoat, so do not export + if (clearcoat.clearcoatFactor == 0.0f) + return false; + + mat.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); + + GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_CLEARCOAT_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission) { + bool result = mat.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmission.transmissionFactor) == aiReturn_SUCCESS; + GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_TRANSMISSION_TEXTURE); + return result || transmission.transmissionTexture.texture; +} + void glTF2Exporter::ExportMaterials() { aiString aiName; for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { - const aiMaterial* mat = mScene->mMaterials[i]; + ai_assert(mScene->mMaterials[i] != nullptr); + + const aiMaterial & mat = *(mScene->mMaterials[i]); std::string id = "material_" + ai_to_string(i); Ref m = mAsset->materials.Create(id); std::string name; - if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { name = aiName.C_Str(); } name = mAsset->FindUniqueID(name, "material"); @@ -660,20 +739,20 @@ void glTF2Exporter::ExportMaterials() GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } - if (mat->Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + 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 m->pbrMetallicRoughness.metallicFactor = 0; } // get roughness if source is gltf2 file - if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { // otherwise, try to derive and convert from specular + shininess values aiColor4D specularColor; ai_real shininess; if ( - mat->Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && - mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS + 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; @@ -694,17 +773,17 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->emissiveTexture, aiTextureType_EMISSIVE); GetMatColor(mat, m->emissiveFactor, AI_MATKEY_COLOR_EMISSIVE); - mat->Get(AI_MATKEY_TWOSIDED, m->doubleSided); - mat->Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided); + mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); aiString alphaMode; - if (mat->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { m->alphaMode = alphaMode.C_Str(); } else { float opacity; - if (mat->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { if (opacity < 1) { m->alphaMode = "BLEND"; m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; @@ -713,86 +792,43 @@ void glTF2Exporter::ExportMaterials() } { - // If has a Specular color, use the KHR_materials_pbrSpecularGlossiness extension + // KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020) PbrSpecularGlossiness pbrSG; - if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { - if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { - mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; - } - - GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); - - // If don't have explicit glossiness then convert from roughness or shininess - if (mat->Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { - float shininess; - if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way - } else if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = shininess / 1000; - } - } - - // Add any appropriate textures - GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); - GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - + if (GetMatSpecGloss(mat, pbrSG)) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; m->pbrSpecularGlossiness = Nullable(pbrSG); } } // glTFv2 is either PBR or Unlit aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; - mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode); + mat.Get(AI_MATKEY_SHADING_MODEL, shadingMode); if (shadingMode == aiShadingMode_Unlit) { mAsset->extensionsUsed.KHR_materials_unlit = true; m->unlit = true; - } + } else { + // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness + if (!m->pbrSpecularGlossiness.isPresent) { + // Sheen + MaterialSheen sheen; + if (GetMatSheen(mat, sheen)) { + mAsset->extensionsUsed.KHR_materials_sheen = true; + m->materialSheen = Nullable(sheen); + } - bool hasMaterialSheen = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN, hasMaterialSheen); + MaterialClearcoat clearcoat; + if (GetMatClearcoat(mat, clearcoat)) { + mAsset->extensionsUsed.KHR_materials_clearcoat = true; + m->materialClearcoat = Nullable(clearcoat); + } - if (hasMaterialSheen) { - mAsset->extensionsUsed.KHR_materials_sheen = true; - - MaterialSheen sheen; - - GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); - GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); - - m->materialSheen = Nullable(sheen); - } - - bool hasMaterialClearcoat = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT, hasMaterialClearcoat); - - if (hasMaterialClearcoat) { - mAsset->extensionsUsed.KHR_materials_clearcoat= true; - - MaterialClearcoat clearcoat; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor); - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); - GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); - - m->materialClearcoat = Nullable(clearcoat); - } - - bool hasMaterialTransmission = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION, hasMaterialTransmission); - - if (hasMaterialTransmission) { - mAsset->extensionsUsed.KHR_materials_transmission = true; - - MaterialTransmission transmission; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR, transmission.transmissionFactor); - GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); - - m->materialTransmission = Nullable(transmission); + MaterialTransmission transmission; + if (GetMatTransmission(mat, transmission)) { + mAsset->extensionsUsed.KHR_materials_transmission = true; + m->materialTransmission = Nullable(transmission); + } + } } } } diff --git a/code/AssetLib/glTF2/glTF2Exporter.h b/code/AssetLib/glTF2/glTF2Exporter.h index 86497516a..edc85d998 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.h +++ b/code/AssetLib/glTF2/glTF2Exporter.h @@ -72,6 +72,10 @@ namespace glTF2 struct OcclusionTextureInfo; struct Node; struct Texture; + struct PbrSpecularGlossiness; + struct MaterialSheen; + struct MaterialClearcoat; + struct MaterialTransmission; // Vec/matrix types, as raw float arrays typedef float (vec2)[2]; @@ -97,15 +101,19 @@ namespace Assimp protected: void WriteBinaryData(IOStream* outfile, std::size_t sceneLength); - void GetTexSampler(const aiMaterial* mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); - void GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTex(const aiMaterial* mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec4& prop, const char* propName, int type, int idx); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec3& prop, const char* propName, int type, int idx); + void GetTexSampler(const aiMaterial& mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); + void GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); + 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 GetMatSheen(const aiMaterial& mat, glTF2::MaterialSheen& sheen); + bool GetMatClearcoat(const aiMaterial& mat, glTF2::MaterialClearcoat& clearcoat); + bool GetMatTransmission(const aiMaterial& mat, glTF2::MaterialTransmission& transmission); void ExportMetadata(); void ExportMaterials(); void ExportMeshes(); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b5ba65857..b0f0955f5 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -291,36 +291,37 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); - //KHR_materials_sheen + // KHR_materials_sheen if (mat.materialSheen.isPresent) { MaterialSheen &sheen = mat.materialSheen.value; - - aimat->AddProperty(&mat.materialSheen.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN); - SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); + // Default value {0,0,0} disables Sheen + if (sheen.sheenColorFactor != defaultSheenFactor) { + SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_SHEEN_COLOR_FACTOR); + aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_SHEEN_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_SHEEN_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + } } - //KHR_materials_clearcoat + // KHR_materials_clearcoat if (mat.materialClearcoat.isPresent) { MaterialClearcoat &clearcoat = mat.materialClearcoat.value; - - aimat->AddProperty(&mat.materialClearcoat.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT); - aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR); - aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); + // Default value 0.0 disables clearcoat + if (clearcoat.clearcoatFactor != 0.0f) { + aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_CLEARCOAT_FACTOR); + aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_CLEARCOAT_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + } } - //KHR_materials_transmission + // KHR_materials_transmission if (mat.materialTransmission.isPresent) { MaterialTransmission &transmission = mat.materialTransmission.value; - aimat->AddProperty(&mat.materialTransmission.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION); - aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); + aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_TRANSMISSION_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_TRANSMISSION_TEXTURE); } return aimat; diff --git a/code/Common/material.cpp b/code/Common/material.cpp index 5230c8b43..6c90e66f0 100644 --- a/code/Common/material.cpp +++ b/code/Common/material.cpp @@ -47,10 +47,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // ------------------------------------------------------------------------------- -const char* TextureTypeToString(aiTextureType in) -{ - switch (in) - { +const char *TextureTypeToString(aiTextureType in) { + switch (in) { case aiTextureType_NONE: return "n/a"; case aiTextureType_DIFFUSE: @@ -87,6 +85,12 @@ const char* TextureTypeToString(aiTextureType in) return "DiffuseRoughness"; case aiTextureType_AMBIENT_OCCLUSION: return "AmbientOcclusion"; + case aiTextureType_SHEEN: + return "Sheen"; + case aiTextureType_CLEARCOAT: + return "Clearcoat"; + case aiTextureType_TRANSMISSION: + return "Transmission"; case aiTextureType_UNKNOWN: return "Unknown"; default: diff --git a/include/assimp/material.h b/include/assimp/material.h index 11cdef1f4..33e39529e 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -144,7 +144,9 @@ enum aiTextureMapMode { enum aiTextureMapping { /** The mapping coordinates are taken from an UV channel. * - * The #AI_MATKEY_UVWSRC key specifies from which UV channel + * #AI_MATKEY_UVWSRC property + * + * Specifies from which UV channel * the texture coordinates are to be taken from (remember, * meshes can have more than one UV channel). */ @@ -292,6 +294,32 @@ enum aiTextureType { aiTextureType_DIFFUSE_ROUGHNESS = 16, aiTextureType_AMBIENT_OCCLUSION = 17, + /** PBR Material Modifiers + * Some modern renderers have further PBR modifiers that may be overlaid + * on top of the 'base' PBR materials for additional realism. + * These use multiple texture maps, so only the base type is directly defined + */ + + /** Sheen + * Generally used to simulate textiles that are covered in a layer of microfibers + * eg velvet + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_sheen + */ + aiTextureType_SHEEN = 19, + + /** Clearcoat + * Simulates a layer of 'polish' or 'laquer' layered on top of a PBR substrate + * https://autodesk.github.io/standard-surface/#closures/coating + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ + aiTextureType_CLEARCOAT = 20, + + /** Transmission + * Simulates transmission through the surface + * May include further information such as wall thickness + */ + aiTextureType_TRANSMISSION = 21, + /** Unknown texture * * A texture reference that does not match any of the definitions @@ -314,7 +342,7 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); // --------------------------------------------------------------------------- /** @brief Defines all shading models supported by the library * - * #AI_MATKEY_SHADING_MODEL + * Property: #AI_MATKEY_SHADING_MODEL * * The list of shading modes has been taken from Blender. * See Blender documentation for more information. The API does @@ -324,6 +352,7 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); * Again, this value is just a hint. Assimp tries to select the shader whose * most common implementation matches the original rendering results of the * 3D modeler which wrote a particular model as closely as possible. + * */ enum aiShadingMode { /** Flat shading. Shading is done on per-face base, @@ -384,10 +413,11 @@ enum aiShadingMode { * There are multiple methods under this banner, and model files may provide * data for more than one PBR-BRDF method. * Applications should use the set of provided properties to determine which - * of their preferred PBDR methods are available + * of their preferred PBR rendering methods are likely to be available * eg: * - If AI_MATKEY_METALLIC_FACTOR is set, then a Metallic/Roughness is available - * - If AI_MATKEY_COLOR_SPECULAR is set, then a Specular/Glossiness is available + * - If AI_MATKEY_GLOSSINESS_FACTOR is set, then a Specular/Glossiness is available + * Note that some PBR methods allow layering of techniques */ aiShadingMode_PBR_BRDF = 0xb, @@ -942,28 +972,63 @@ extern "C" { // --------------------------------------------------------------------------- // PBR material support +// -------------------- +// Properties defining PBR rendering techniques #define AI_MATKEY_USE_COLOR_MAP "$mat.useColorMap", 0, 0 // Metallic/Roughness Workflow // --------------------------- -// Base color factor. Will be multiplied by final base color texture values if extant +// Base RGBA color factor. Will be multiplied by final base color texture values if extant +// Note: Importers may choose to copy this into AI_MATKEY_COLOR_DIFFUSE for compatibility +// with renderers and formats that do not support Metallic/Roughness PBR #define AI_MATKEY_BASE_COLOR "$clr.base", 0, 0 +#define AI_MATKEY_BASE_COLOR_TEXTURE aiTextureType_BASE_COLOR, 0 #define AI_MATKEY_USE_METALLIC_MAP "$mat.useMetallicMap", 0, 0 // Metallic factor. 0.0 = Full Dielectric, 1.0 = Full Metal #define AI_MATKEY_METALLIC_FACTOR "$mat.metallicFactor", 0, 0 +#define AI_MATKEY_METALLIC_TEXTURE aiTextureType_METALNESS, 0 #define AI_MATKEY_USE_ROUGHNESS_MAP "$mat.useRoughnessMap", 0, 0 // Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 +#define AI_MATKEY_ROUGHNESS_TEXTURE aiTextureType_DIFFUSE_ROUGHNESS, 0 // Specular/Glossiness Workflow // --------------------------- // Diffuse/Albedo Color. Note: Pure Metals have a diffuse of {0,0,0} // AI_MATKEY_COLOR_DIFFUSE -// Specular Color +// Specular Color. +// Note: Metallic/Roughness may also have a Specular Color // AI_MATKEY_COLOR_SPECULAR +#define AI_MATKEY_SPECULAR_FACTOR "$mat.specularFactor", 0, 0 // Glossiness factor. 0.0 = Completely Rough, 1.0 = Perfectly Smooth #define AI_MATKEY_GLOSSINESS_FACTOR "$mat.glossinessFactor", 0, 0 +// Sheen +// ----- +// Sheen base RGB color. Default {0,0,0} +#define AI_MATKEY_SHEEN_COLOR_FACTOR "$clr.sheen.factor", 0, 0 +// Sheen Roughness Factor. +#define AI_MATKEY_SHEEN_ROUGHNESS_FACTOR "$mat.sheen.roughnessFactor", 0, 0 +#define AI_MATKEY_SHEEN_COLOR_TEXTURE aiTextureType_SHEEN, 0 +#define AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE aiTextureType_SHEEN, 1 + +// Clearcoat +// --------- +#define AI_MATKEY_CLEARCOAT_FACTOR "$clr.clearcoat.factor", 0, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR "$mat.clearcoat.roughnessFactor", 0, 0 +#define AI_MATKEY_CLEARCOAT_TEXTURE aiTextureType_CLEARCOAT, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_CLEARCOAT, 1 +#define AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE aiTextureType_CLEARCOAT, 2 + +// Transmission +// ------------ +// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission +// Base percentage of light transmitted through the surface. 0.0 = Opaque, 1.0 = Fully transparent +#define AI_MATKEY_TRANSMISSION_FACTOR "$mat.transmission.factor", 0, 0 +// Texture defining percentage of light transmitted through the surface. +// Multiplied by AI_MATKEY_TRANSMISSION_FACTOR +#define AI_MATKEY_TRANSMISSION_TEXTURE aiTextureType_TRANSMISSION, 0 + // Emissive // -------- #define AI_MATKEY_USE_EMISSIVE_MAP "$mat.useEmissiveMap", 0, 0 diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index fd904e4fc..c67bcc3b8 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -60,20 +60,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 //#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0 //#define AI_MATKEY_GLTF_UNLIT "$mat.gltf.unlit", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE aiTextureType_UNKNOWN, 1 -#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 2 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT "$mat.gltf.materialClearcoat", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR "$mat.gltf.materialClearcoat.clearcoatFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR "$mat.gltf.materialClearcoat.clearcoatRoughnessFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE aiTextureType_UNKNOWN, 3 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 4 -#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE aiTextureType_NORMALS, 1 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION "$mat.gltf.materialTransmission", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 -#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN "$mat.gltf.materialSheen", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR "$mat.gltf.materialSheen.sheenColorFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR "$mat.gltf.materialSheen.sheenRoughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE aiTextureType_UNKNOWN, 1 +//#define AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 2 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT "$mat.gltf.materialClearcoat", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR "$mat.gltf.materialClearcoat.clearcoatFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR "$mat.gltf.materialClearcoat.clearcoatRoughnessFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE aiTextureType_UNKNOWN, 3 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 4 +//#define AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE aiTextureType_NORMALS, 1 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION "$mat.gltf.materialTransmission", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 +//#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 #define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" #define _AI_MATKEY_GLTF_MAPPINGNAME_BASE "$tex.mappingname" diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index e0ac10ad5..766372325 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -152,6 +152,20 @@ TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { } #ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossiness) { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb")); + + // And re-import + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true)); +} + TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; Assimp::Exporter exporter; @@ -169,6 +183,7 @@ TEST_F(utglTF2ImportExport, importglTF2EmbeddedAndExportToOBJ) { EXPECT_NE(nullptr, scene); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured_out.obj")); } + #endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, importglTF2PrimitiveModePointsWithoutIndices) { From fb039bb9ebcba96602f7ba63bcee8d481f6eccc5 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 14:34:42 +0100 Subject: [PATCH 24/44] Add glTFv2 Clearcoat import/export tests Uses Clearcoat model from Khronos --- code/AssetLib/glTF2/glTF2Importer.cpp | 5 + include/assimp/material.h | 3 +- .../glTF2/ClearCoat-glTF/ClearCoatLabels.png | Bin 0 -> 10270 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.bin | Bin 0 -> 50328 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.gltf | 1669 +++++++++++++++++ .../glTF2/ClearCoat-glTF/PartialCoating.png | Bin 0 -> 5077 bytes .../ClearCoat-glTF/PartialCoating_Alpha.png | Bin 0 -> 5065 bytes .../ClearCoat-glTF/PlasticWrap_normals.jpg | Bin 0 -> 144210 bytes .../glTF2/ClearCoat-glTF/RibsNormal.png | Bin 0 -> 1605 bytes .../glTF2/ClearCoat-glTF/RoughnessStripes.png | Bin 0 -> 5033 bytes test/unit/utglTF2ImportExport.cpp | 57 + 11 files changed, 1733 insertions(+), 1 deletion(-) create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg create mode 100644 test/models/glTF2/ClearCoat-glTF/RibsNormal.png create mode 100644 test/models/glTF2/ClearCoat-glTF/RoughnessStripes.png diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b0f0955f5..b435f111d 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -208,6 +208,11 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset if (sampler->minFilter != SamplerMinFilter::UNSET) { mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); } + } else { + // Use glTFv2 default sampler + const aiTextureMapMode default_wrap = aiTextureMapMode_Wrap; + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); } } } diff --git a/include/assimp/material.h b/include/assimp/material.h index 33e39529e..f348da369 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -1014,7 +1014,8 @@ extern "C" { // Clearcoat // --------- -#define AI_MATKEY_CLEARCOAT_FACTOR "$clr.clearcoat.factor", 0, 0 +// Clearcoat layer intensity. 0.0 = none (disabled) +#define AI_MATKEY_CLEARCOAT_FACTOR "$mat.clearcoat.factor", 0, 0 #define AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR "$mat.clearcoat.roughnessFactor", 0, 0 #define AI_MATKEY_CLEARCOAT_TEXTURE aiTextureType_CLEARCOAT, 0 #define AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_CLEARCOAT, 1 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png b/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png new file mode 100644 index 0000000000000000000000000000000000000000..d47f2f5e1f06116fca5ddd9697001ffedcc5bd68 GIT binary patch literal 10270 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 literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7c6a93a4995917f931d580f7d25c94f33d1639a GIT binary patch literal 50328 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 z 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 MV6NHY-@g5S0BBn6`Tzg` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf new file mode 100644 index 000000000..ae12bfc11 --- /dev/null +++ b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf @@ -0,0 +1,1669 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.2.8 with hand-edits for clearcoat", + "version" : "2.0" + }, + "extensionsUsed": [ + "KHR_materials_clearcoat" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 3, + 7, + 11, + 15, + 19, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 1, + "name" : "ClearCoatSample" + }, + { + "mesh" : 2, + "name" : "CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 0, + 1, + 2 + ], + "name" : "R0_SimpleCoatTest", + "translation" : [ + 0, + 5.25, + 0 + ] + }, + { + "mesh" : 3, + "name" : "R1_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 4, + "name" : "R1_ClearCoatSample" + }, + { + "mesh" : 5, + "name" : "R1_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 4, + 5, + 6 + ], + "name" : "R1_PartialCoatTest", + "translation" : [ + 0, + 3.1500000953674316, + 0 + ] + }, + { + "mesh" : 6, + "name" : "R2_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 7, + "name" : "R2_ClearCoatSample" + }, + { + "mesh" : 8, + "name" : "R2_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 8, + 9, + 10 + ], + "name" : "R2_RoughnessVariations", + "translation" : [ + 0, + 1.0499999523162842, + 0 + ] + }, + { + "mesh" : 9, + "name" : "R3_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 10, + "name" : "R3_ClearCoatSample" + }, + { + "mesh" : 11, + "name" : "R3_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 12, + 13, + 14 + ], + "name" : "R3_BaseNormals", + "translation" : [ + 0, + -1.0499999523162842, + 0 + ] + }, + { + "mesh" : 12, + "name" : "R4_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 13, + "name" : "R4_ClearCoatSample" + }, + { + "mesh" : 14, + "name" : "R4_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 16, + 17, + 18 + ], + "name" : "R4_CoatNormals", + "translation" : [ + 0, + -5.25, + 0 + ] + }, + { + "mesh" : 15, + "name" : "R5_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 16, + "name" : "R5_ClearCoatSample" + }, + { + "mesh" : 17, + "name" : "R5_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 20, + 21, + 22 + ], + "name" : "R5_SharedNormals", + "translation" : [ + 0, + -3.1500000953674316, + 0 + ] + }, + { + "mesh" : 18, + "name" : "X2_Label_CoatingOnly", + "translation" : [ + 2.0712804794311523, + 6.619500160217285, + 0 + ] + }, + { + "mesh" : 19, + "name" : "Y0_Label_SimpleCoating", + "translation" : [ + -5.3578033447265625, + 5.25, + 0 + ] + }, + { + "mesh" : 20, + "name" : "Y1_Label_PartialCoating", + "translation" : [ + -5.3578033447265625, + 3.1673200130462646, + 0 + ] + }, + { + "mesh" : 21, + "name" : "Y2_Label_Roughness", + "translation" : [ + -5.3578033447265625, + 1.1383899450302124, + 0 + ] + }, + { + "mesh" : 22, + "name" : "Y3_Label_BaseNormals", + "translation" : [ + -5.3578033447265625, + -1.099429965019226, + 0 + ] + }, + { + "mesh" : 23, + "name" : "Y4_Label_CoatNormals", + "translation" : [ + -5.3578033447265625, + -5.252500057220459, + 0 + ] + }, + { + "mesh" : 24, + "name" : "Y5_Label_SharedNormals", + "translation" : [ + -5.3578033447265625, + -3.1963000297546387, + 0 + ] + }, + { + "mesh" : 25, + "name" : "X0_Label_BaseLayer", + "translation" : [ + -2.087031602859497, + 6.616230010986328, + 0 + ] + }, + { + "mesh" : 26, + "name" : "X1_Label_Coated", + "translation" : [ + 0, + 6.614150047302246, + 0 + ] + } + ], + "materials" : [ + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatTexture": { + "index": 0, + "texCoord": 0 + } + } + } + }, + { + "alphaMode" : "BLEND", + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coating", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1, + "texCoord" : 0 + }, + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 1, + "clearcoatRoughnessTexture": { + "index": 2, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "metallicRoughnessTexture" : { + "index" : 2, + "texCoord" : 0 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Base", + "normalTexture" : { + "index" : 3, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coated", + "normalTexture" : { + "index" : 4, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012848637998104095, + 0.021861059591174126, + 0.11068868637084961, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 5, + "texCoord": 0, + "scale": 1 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coating", + "normalTexture" : { + "index" : 5, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Base", + "normalTexture" : { + "index" : 6, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coated", + "normalTexture" : { + "index" : 7, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 7, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coating", + "normalTexture" : { + "index" : 8, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 1, + 1, + 1 + ], + "emissiveTexture" : { + "index" : 9, + "texCoord" : 0 + }, + "name" : "LabelMaterial", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.8999999761581421 + } + } + ], + "meshes" : [ + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 1 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 2 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 3 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 4 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 5 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 6 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 7 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 8 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 9 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 10 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 11 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 12 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 13 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 14 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 15 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 16 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 17 + } + ] + }, + { + "name" : "Labels_Mesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 8, + "NORMAL" : 9, + "TEXCOORD_0" : 10 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.002", + "primitives" : [ + { + "attributes" : { + "POSITION" : 11, + "NORMAL" : 12, + "TEXCOORD_0" : 13 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 15, + "NORMAL" : 16, + "TEXCOORD_0" : 17 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.004", + "primitives" : [ + { + "attributes" : { + "POSITION" : 18, + "NORMAL" : 19, + "TEXCOORD_0" : 20 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.005", + "primitives" : [ + { + "attributes" : { + "POSITION" : 21, + "NORMAL" : 22, + "TEXCOORD_0" : 23 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.006", + "primitives" : [ + { + "attributes" : { + "POSITION" : 24, + "NORMAL" : 25, + "TEXCOORD_0" : 26 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.007", + "primitives" : [ + { + "attributes" : { + "POSITION" : 27, + "NORMAL" : 28, + "TEXCOORD_0" : 29 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.008", + "primitives" : [ + { + "attributes" : { + "POSITION" : 30, + "NORMAL" : 31, + "TEXCOORD_0" : 32 + }, + "indices" : 7, + "material" : 18 + } + ] + } + ], + "textures" : [ + { + "source" : 0 + }, + { + "source" : 1 + }, + { + "source" : 2 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 4 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 5 + } + ], + "images" : [ + { + "mimeType" : "image/png", + "name" : "PartialCoating", + "uri" : "PartialCoating.png" + }, + { + "mimeType" : "image/png", + "name" : "PartialCoating_Alpha", + "uri" : "PartialCoating_Alpha.png" + }, + { + "mimeType" : "image/png", + "name" : "RoughnessStripes", + "uri" : "RoughnessStripes.png" + }, + { + "mimeType" : "image/png", + "name" : "RibsNormal", + "uri" : "RibsNormal.png" + }, + { + "mimeType" : "image/jpeg", + "name" : "PlasticWrap_normals", + "uri" : "PlasticWrap_normals.jpg" + }, + { + "mimeType" : "image/png", + "name" : "ClearCoatLabels", + "uri" : "ClearCoatLabels.png" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 1113, + "max" : [ + 1, + 1, + 1.0499999523162842 + ], + "min" : [ + -1, + -1, + -0.06000000983476639 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 6180, + "type" : "SCALAR" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 8, + "max" : [ + 1.0280373096466064, + 0.23501670360565186, + 3.8289083903464416e-08 + ], + "min" : [ + -0.968224287033081, + -0.2350165843963623, + -0.010000125505030155 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 7, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.23026323318481445, + 3.751463495405005e-08 + ], + "min" : [ + -2, + -0.23026317358016968, + -0.010000579059123993 + ], + "type" : "VEC3" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 11, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.2302631139755249, + 3.7514624295909016e-08 + ], + "min" : [ + -2, + -0.23026323318481445, + -0.010000428184866905 + ], + "type" : "VEC3" + }, + { + "bufferView" : 12, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 13, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 14, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 15, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22039484977722168, + 3.590687924770464e-08 + ], + "min" : [ + -2, + -0.22039473056793213, + -0.010000280104577541 + ], + "type" : "VEC3" + }, + { + "bufferView" : 16, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.21764808893203735, + 3.545937588000925e-08 + ], + "min" : [ + -2, + -0.21764802932739258, + -0.010000137612223625 + ], + "type" : "VEC3" + }, + { + "bufferView" : 19, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 20, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.20775499939918518, + 3.3847587843638394e-08 + ], + "min" : [ + -2, + -0.20775499939918518, + -0.009999996051192284 + ], + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 23, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 24, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22341907024383545, + 3.6399587344249085e-08 + ], + "min" : [ + -2, + -0.22341907024383545, + -0.009999859146773815 + ], + "type" : "VEC3" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 27, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.9169960618019104, + 0.22670458257198334, + 3.69348676088066e-08 + ], + "min" : [ + -0.9199233651161194, + -0.22670456767082214, + -0.010000176727771759 + ], + "type" : "VEC3" + }, + { + "bufferView" : 28, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.8968609571456909, + 0.20853587985038757, + 3.397480696776256e-08 + ], + "min" : [ + -0.9147982001304626, + -0.2085357904434204, + -0.010000113397836685 + ], + "type" : "VEC3" + }, + { + "bufferView" : 31, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 32, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 13356 + }, + { + "buffer" : 0, + "byteLength" : 8904, + "byteOffset" : 26712 + }, + { + "buffer" : 0, + "byteLength" : 12360, + "byteOffset" : 35616 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 47976 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48072 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48168 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48232 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48256 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48352 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48448 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48512 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48608 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48704 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48768 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48792 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48888 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48984 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49048 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49144 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49240 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49304 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49400 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49496 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49560 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49656 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49752 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49816 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49912 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50008 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50072 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50168 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50264 + } + ], + "buffers" : [ + { + "byteLength" : 50328, + "uri" : "ClearCoatTest.bin" + } + ] +} diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating.png new file mode 100644 index 0000000000000000000000000000000000000000..d579bbebb8a423eedb16594a7ef715fe7521a752 GIT binary patch literal 5077 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 Q@JSA$qoJ>ky=fQyKcnMWr2qf` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..b1911d9f0a303a57181d44856f3f31ea18c11063 GIT binary patch literal 5065 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 Q_!|ylXYGipvh+*(4_Os-!2kdN literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg b/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73e6260ef7126ccb11113179c58cf6611d3f3e9d GIT binary patch literal 144210 zcmbTddpy&9{QtkM4mwFHL}gd0Tv3)p6n2#džOVOJDoNkR^>T_xnWONE@;Rbq*4 zIn0@rb2*>3a?BiOIgBl{!+XD%>+?JP@%`ib`wh3fUNvRhT{&|%F#j%c0KH#lWzblU9v1#^pwmR5Gx>>be89Wm}V zZ+Up$zT@TRAMh|RC^#hiX+&hyv*?)TFJ2}kr@Tr{dz!pljPw6`{4d9)2*&l> zx^-*U$*qoS&2NF=XRYG8_4{=<>^OTx?uPHq11BDD+;uMTO~L0)8hTe3l-wS4ZB{;b za`X^yb!h*M?0;`yPyT<6?EfCv|1++B*w(dcz~rq}gdt&#orEzV+B#P%5TW(|mBE@8 zS=JvGIDFd7SL`O?FBfPEX_wMR7@O`>;h^bu&`Yma)=B7aXdi*y(i>1J>Z1 zz1CTd`n94v@k@%hY{oRAvM!yOmqt`BfRxOCOs>p8Il6b?qs|=`>mHp9NH~CHPDbQ; z^oD7};Qa5Vbo$NjC2*@cyYwj$^JG%CMwAih&>(lyF~Q^4J92YNnA906$eL`kJ?7NQ}nL{3T+omFSO1Ea7EQ zTxSKZ_o0e>q!F98vd4Y}Z_XsjVCpFQd==<$452>{V<>~2^J#qB!-5X~DQ4yFd#eRL zee{YtiA-pEFG8mWeJ;wShuB0ZXK`FT*>#y$7;1}uAVP9Qo_nmJ9n&W9 z%!KlIG!>J9Ipm+Chwh$Od4VOo_sRCdsGROWTfQf}_nP$IDTD1KQ$tIoUujv&Au`yg z_2#Os@77RmP0}QEXm@!CKh-F{CTU7t6SNJ(xF_%PXyRhW>>q?OJe}z*9d#x>hDJp! zC*R&{li2yZPVQ*O%Fa&{l8X-unY5?d{?aG1!y~a2FX8dY4K#~gj8P*ITt6~X7};cb z$PY6$qSSpcnCjz<)DNArk@(FmO+d{zhKc+en0`%32(7!2T25EWd&|4auoC^mE|_}O zO;~+vk-_{siFsk#&v-s|0_ud^Fm+EuCW={JIL~gLW)oIaq!Zp7*?RnZAH@<+ipwsx z-u3<^4?ovFmsH^|PlOQlM0X_ne8@H8vX_B=BQZL9+Eot8Lr~{!!5Z~*R(XOPt7iq@ zXeg(h(Q};|Bv{^mA%oq@hIY1A-Pe1h$$URRlUR2b8{=d!cwv}Vc7+U9Uyd8j>gj^N zV8$FRT5q+iu@!XA;r8RJ;G4EC_w0f5Gvz61e`AsA6+!kBLfKtWTmx1~Y5dOTT4vz0vYu>_Lx!1B3foKu+rhynMZ%q} z1SUtDhCW@B!L}I3_y{@#l!3KS_`bhdAUZ^^{EN`}ye5WJ-!aj;NBXllz*qcOkiQ9~ z-|3Yl-OVzU6y%Z`Z^dA14)vs~z~e&>zc_r zt70#$&;vxQ!1umnQuh5ccv~KgSy_#WUCXB-R3#W(t5hyRMXY?-m8|&zE=-0~?vGD` zMaQ~^`-^Am6(+Jhg zP~0eXW#ZA@U(V`qK~l%`F%Y}jz4SuaR?`b6~~l7y`jt`!3FzW4P9%7`&Y-nERpCt&zbJ9|=GRiZ(OYB%peJ))q5GEEI3AM+2VT9(2^r%(Z>3wB0nS-s6MBo?zDb zFY-9mq6t?K#LfF#!gcP5PdV|1dUr>O#&{TZr)g-3b=%&C&kc!6af6AN0Uh{z6V~;pYpC zPcf_NZkKqzS*ZI?@E*1?SPBO63urDg7O z>xgW#FL()mE@@w;+~P8LGdP=V=!N{YK*+?#w@cW?|27eNawM^}zvPgTMLk*X7$&0N zcTpd5`_ch)fX`D2VISg`LsgeTWV?QlDV_UO2Gc?lg`s2iQC!!eJ>%xu?Rk3|5xpW? zC6dmL4@>5UKj^(Tx%rk*|8>orw)<7pUiBh(lrU|8_$wlyZq#tbEFA<-ABg;}UROk6)ZhT~SS>VDG9MhJlyrJh4{MWLdDWHNJb7i3Dpk

8#fXvU&U@TnHe66M)}` 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≫PJyOL#2noO42?ak;#zU>=*qI;o{}k?8jYM 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�-h@x9GW3t>NHF#%4~dx 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 literal 0 HcmV?d00001 diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 766372325..ee8b1a742 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -151,8 +151,65 @@ TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); } +void VerifyClearCoatScene(const aiScene *scene) { + ASSERT_NE(nullptr, scene); + + ASSERT_TRUE(scene->HasMaterials()); + + // Find a specific Clearcoat material and check the values + const aiString partial_coated("Partial_Coated"); + bool found_partial_coat = false; + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + const aiMaterial *material = scene->mMaterials[i]; + ASSERT_NE(nullptr, material); + if (material->GetName() == partial_coated) { + found_partial_coat = true; + + ai_real clearcoat_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat_factor)); + EXPECT_EQ(ai_real(1.0f), clearcoat_factor); + + ai_real clearcoat_rough_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat_rough_factor)); + EXPECT_EQ(ai_real(0.03f), clearcoat_rough_factor); + + // Should import the texture as diffuse and as base color + aiString path; + std::array modes; + static const std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_CLEARCOAT_TEXTURE, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "PartialCoating.png"); + EXPECT_EQ(exp_modes, modes); + } + } + EXPECT_TRUE(found_partial_coat); +} + +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_clearcoat) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + #ifndef ASSIMP_BUILD_NO_EXPORT +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_clearcoat) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb")); + } + + // And re-import + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossiness) { Assimp::Importer importer; Assimp::Exporter exporter; From 985f3ee6650983582b8612217f236f8c46a274e3 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:17:25 +0100 Subject: [PATCH 25/44] Fix glTFv2 texcoord/uv mapping Use the standard property to indicate the UV map index --- code/AssetLib/glTF2/glTF2Exporter.cpp | 21 ++++++----- code/AssetLib/glTF2/glTF2Exporter.h | 2 +- code/AssetLib/glTF2/glTF2Importer.cpp | 3 +- include/assimp/material.h | 4 +-- include/assimp/pbrmaterial.h | 4 +-- test/unit/utglTF2ImportExport.cpp | 50 ++++++++++++++++++++------- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 3cb3891b0..7e0966aff 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -492,11 +492,14 @@ void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, float& prop, const char mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot = 0) { if (mat.GetTextureCount(tt) > 0) { aiString tex; + // Read texcoord (UV map index) + mat.Get(AI_MATKEY_UVWSRC(tt, slot), texCoord); + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); @@ -572,21 +575,21 @@ void glTF2Exporter::GetMatTex(const aiMaterial& mat, TextureInfo& prop, aiTextur { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + GetMatTex(mat, texture, prop.texCoord, tt, slot); - if (texture) { - GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); - } + //if (texture) { + // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + //} } void glTF2Exporter::GetMatTex(const aiMaterial& mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + 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); } } @@ -595,10 +598,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial& mat, OcclusionTextureInfo& prop, { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + 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); } } diff --git a/code/AssetLib/glTF2/glTF2Exporter.h b/code/AssetLib/glTF2/glTF2Exporter.h index edc85d998..f5238297f 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.h +++ b/code/AssetLib/glTF2/glTF2Exporter.h @@ -104,7 +104,7 @@ namespace Assimp void GetTexSampler(const aiMaterial& mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); void GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); void GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); void GetMatTex(const aiMaterial& mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index b435f111d..aadc9fb16 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -165,7 +165,8 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); - mat->AddProperty(&prop.texCoord, 1, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(texType, texSlot)); + const int uvIndex = static_cast(prop.texCoord); + mat->AddProperty(&uvIndex, 1, AI_MATKEY_UVWSRC(texType, texSlot)); if (prop.textureTransformSupported) { aiUVTransform transform; diff --git a/include/assimp/material.h b/include/assimp/material.h index f348da369..2024be07f 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -144,9 +144,7 @@ enum aiTextureMapMode { enum aiTextureMapping { /** The mapping coordinates are taken from an UV channel. * - * #AI_MATKEY_UVWSRC property - * - * Specifies from which UV channel + * #AI_MATKEY_UVWSRC property specifies from which UV channel * the texture coordinates are to be taken from (remember, * meshes can have more than one UV channel). */ diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index c67bcc3b8..93e7e3095 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -75,7 +75,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR "$mat.gltf.materialTransmission.transmissionFactor", 0, 0 //#define AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE aiTextureType_UNKNOWN, 5 -#define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" +//#define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord" #define _AI_MATKEY_GLTF_MAPPINGNAME_BASE "$tex.mappingname" #define _AI_MATKEY_GLTF_MAPPINGID_BASE "$tex.mappingid" #define _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE "$tex.mappingfiltermag" @@ -83,7 +83,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define _AI_MATKEY_GLTF_SCALE_BASE "$tex.scale" #define _AI_MATKEY_GLTF_STRENGTH_BASE "$tex.strength" -#define AI_MATKEY_GLTF_TEXTURE_TEXCOORD(type, N) _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, type, N +//#define AI_MATKEY_GLTF_TEXTURE_TEXCOORD(type, N) _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGNAME(type, N) _AI_MATKEY_GLTF_MAPPINGNAME_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGID(type, N) _AI_MATKEY_GLTF_MAPPINGID_BASE, type, N #define AI_MATKEY_GLTF_MAPPINGFILTER_MAG(type, N) _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE, type, N diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index ee8b1a742..2c000bb37 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -603,32 +603,58 @@ TEST_F(utglTF2ImportExport, sceneMetadata) { } TEST_F(utglTF2ImportExport, texcoords) { + Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_TRUE(scene->HasMaterials()); + const aiMaterial *material = scene->mMaterials[0]; + + aiString path; + unsigned int uvIndex = 255; + aiTextureMapMode modes[2]; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 0); + + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 1); +} + +#ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, texcoords_export) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf_out.glb")); + } + + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); ASSERT_NE(scene, nullptr); ASSERT_TRUE(scene->HasMaterials()); const aiMaterial *material = scene->mMaterials[0]; aiString path; + unsigned int uvIndex = 255; aiTextureMapMode modes[2]; - EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); EXPECT_STREQ(path.C_Str(), "texture.png"); - - int uvIndex = -1; - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_DIFFUSE, 0), &uvIndex), aiReturn_SUCCESS); EXPECT_EQ(uvIndex, 0); - // Using manual macro expansion of AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE here. - // The following works with some but not all compilers: - // #define APPLY(X, Y) X(Y) - // ..., APPLY(AI_MATKEY_GLTF_TEXTURE_TEXCOORD, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE), ... - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_UNKNOWN, 0), &uvIndex), aiReturn_SUCCESS); + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); EXPECT_EQ(uvIndex, 1); } +#endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, recursive_nodes) { Assimp::Importer importer; const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/RecursiveNodes/RecursiveNodes.gltf", aiProcess_ValidateDataStructure); From f412595887647d77cd476576a3c970bfae01c4eb Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:25:02 +0100 Subject: [PATCH 26/44] Fix typo Thank you clang! --- code/AssetLib/glTF2/glTF2Exporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 7e0966aff..40ba41c20 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -655,7 +655,7 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo // Add any appropriate textures GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - result == result || pbrSG.specularGlossinessTexture.texture; + result = result || pbrSG.specularGlossinessTexture.texture; if (result) { // Likely to always have diffuse From af748755e186faf5a80e597a37122ed55b6d3fcb Mon Sep 17 00:00:00 2001 From: Hill Ma Date: Tue, 15 Jun 2021 13:20:12 -0700 Subject: [PATCH 27/44] Fix issue #2873 --- code/PostProcessing/EmbedTexturesProcess.cpp | 23 +++++++++++++------- code/PostProcessing/EmbedTexturesProcess.h | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index 7e435e556..ba978dbde 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -41,6 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "EmbedTexturesProcess.h" +#include +#include #include #include "ProcessHelper.h" @@ -62,6 +64,7 @@ bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { void EmbedTexturesProcess::SetupProperties(const Importer* pImp) { mRootPath = pImp->GetPropertyString("sourceFilePath"); mRootPath = mRootPath.substr(0, mRootPath.find_last_of("\\/") + 1u); + mIOHandler = pImp->GetIOHandler(); } void EmbedTexturesProcess::Execute(aiScene* pScene) { @@ -101,27 +104,31 @@ bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const { std::string imagePath = path; // Test path directly - std::ifstream file(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + 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; - file.open(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + if (!mIOHandler->Exists(imagePath)) { // Test path basename in root path imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u); - file.open(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + if (!mIOHandler->Exists(imagePath)) { ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); return false; } } } + IOStream* pFile = mIOHandler->Open(imagePath); + if (pFile == nullptr) { + ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); + return false; + } + imageSize = pFile->FileSize(); aiTexel* imageContent = new aiTexel[ 1ul + static_cast( imageSize ) / sizeof(aiTexel)]; - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(imageContent), imageSize); + pFile->Seek(0, aiOrigin_SET); + pFile->Read(reinterpret_cast(imageContent), imageSize, 1); + mIOHandler->Close(pFile); // Enlarging the textures table unsigned int textureId = pScene->mNumTextures++; diff --git a/code/PostProcessing/EmbedTexturesProcess.h b/code/PostProcessing/EmbedTexturesProcess.h index 90970937a..5915e0d44 100644 --- a/code/PostProcessing/EmbedTexturesProcess.h +++ b/code/PostProcessing/EmbedTexturesProcess.h @@ -48,6 +48,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiNode; +class IOSystem; + namespace Assimp { /** @@ -80,6 +82,7 @@ private: private: std::string mRootPath; + IOSystem* mIOHandler = nullptr; }; } // namespace Assimp From 148b8c66a8ac040cc859444a27b5c7f3e061232a Mon Sep 17 00:00:00 2001 From: Hill Ma Date: Tue, 15 Jun 2021 15:18:20 -0700 Subject: [PATCH 28/44] glTF2: zero out extra space created by padding. This makes resulting GLB deterministic. --- code/AssetLib/glTF2/glTF2Asset.inl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index b51ac20c2..c58732a03 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -790,8 +790,10 @@ inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const siz inline size_t Buffer::AppendData(uint8_t *data, size_t length) { size_t offset = this->byteLength; // Force alignment to 4 bits - Grow((length + 3) & ~3); + size_t paddedLength = (length + 3) & ~3; + Grow(paddedLength); memcpy(mData.get() + offset, data, length); + memset(mData.get() + offset + length, 0, paddedLength - length); return offset; } From a34b9d1c95fc60d6b5a91ef998271097b2d2413d Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 16 Jun 2021 11:21:31 +0200 Subject: [PATCH 29/44] Fix review findings --- code/PostProcessing/EmbedTexturesProcess.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index ba978dbde..cb2853926 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -50,11 +49,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -EmbedTexturesProcess::EmbedTexturesProcess() -: BaseProcess() { +EmbedTexturesProcess::EmbedTexturesProcess() : + BaseProcess() { + // empty } EmbedTexturesProcess::~EmbedTexturesProcess() { + // empty } bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { @@ -68,12 +69,12 @@ void EmbedTexturesProcess::SetupProperties(const Importer* pImp) { } void EmbedTexturesProcess::Execute(aiScene* pScene) { - if (pScene == nullptr || pScene->mRootNode == nullptr) return; + if (pScene == nullptr || pScene->mRootNode == nullptr || mIOHandler == nullptr){ + return; + } aiString path; - uint32_t embeddedTexturesCount = 0u; - for (auto matId = 0u; matId < pScene->mNumMaterials; ++matId) { auto material = pScene->mMaterials[matId]; From e27074594f7444104bbe796feee041b5affa4233 Mon Sep 17 00:00:00 2001 From: Andreas Buhr Date: Wed, 16 Jun 2021 18:58:41 +0200 Subject: [PATCH 30/44] Replace swear words in IFCBoolean.cpp Add a more gentle comment. --- code/AssetLib/IFC/IFCBoolean.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/IFC/IFCBoolean.cpp b/code/AssetLib/IFC/IFCBoolean.cpp index 86cac7f46..afd0ad6eb 100644 --- a/code/AssetLib/IFC/IFCBoolean.cpp +++ b/code/AssetLib/IFC/IFCBoolean.cpp @@ -513,7 +513,7 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly } // we got a list of in-out-combinations of intersections. That should be an even number of intersections, or - // we're fucked. + // we are facing a non-recoverable error. if ((intersections.size() & 1) != 0) { IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check."); continue; From a8c75c34a1dd297dfed74e660d30b83d4acc9a56 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 17 Jun 2021 21:31:28 +0200 Subject: [PATCH 31/44] Update scene.h Add some more checks against nullptr dereferecnes. --- include/assimp/scene.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/include/assimp/scene.h b/include/assimp/scene.h index fb3b2ef6e..522ddc6dc 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -402,15 +400,23 @@ struct aiScene //! Returns an embedded texture and its index std::pair GetEmbeddedTextureAndIndex(const char* filename) const { + if(nullptr==filename) { + return std::make_pair(nullptr, -1); + } // lookup using texture ID (if referenced like: "*1", "*2", etc.) if ('*' == *filename) { int index = std::atoi(filename + 1); - if (0 > index || mNumTextures <= static_cast(index)) + if (0 > index || mNumTextures <= static_cast(index)) { return std::make_pair(nullptr, -1); + } return std::make_pair(mTextures[index], index); } // lookup using filename const char* shortFilename = GetShortFilename(filename); + if (nullptr == shortFilename) { + return std::make_pair(nullptr, -1); + } + for (unsigned int i = 0; i < mNumTextures; i++) { const char* shortTextureFilename = GetShortFilename(mTextures[i]->mFilename.C_Str()); if (strcmp(shortTextureFilename, shortFilename) == 0) { From 94c3abd841dd08d8c98d1c7cbbf953ffb314bd94 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 22 Jun 2021 12:27:15 -0400 Subject: [PATCH 32/44] Apply various performance fixes from clang-tidy --- code/AssetLib/3DS/3DSHelper.h | 76 ++++++++++---------- code/AssetLib/ASE/ASEParser.h | 8 +-- code/AssetLib/B3D/B3DImporter.cpp | 2 +- code/AssetLib/B3D/B3DImporter.h | 2 +- code/AssetLib/Blender/BlenderLoader.cpp | 2 +- code/AssetLib/COB/COBLoader.cpp | 2 +- code/AssetLib/Collada/ColladaParser.cpp | 2 +- code/AssetLib/FBX/FBXConverter.cpp | 2 +- code/AssetLib/FBX/FBXDocument.cpp | 9 ++- code/AssetLib/FBX/FBXExportNode.cpp | 5 +- code/AssetLib/FBX/FBXExportNode.h | 5 +- code/AssetLib/FBX/FBXExporter.cpp | 17 ++--- code/AssetLib/FBX/FBXExporter.h | 15 ++-- code/AssetLib/FBX/FBXProperties.cpp | 9 ++- code/AssetLib/IFC/IFCGeometry.cpp | 2 +- code/AssetLib/IFC/IFCMaterial.cpp | 6 +- code/AssetLib/IFC/IFCUtil.h | 16 ++--- code/AssetLib/LWS/LWSLoader.cpp | 2 +- code/AssetLib/M3D/M3DImporter.cpp | 2 +- code/AssetLib/M3D/M3DImporter.h | 4 +- code/AssetLib/Ogre/OgreMaterial.cpp | 4 +- code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp | 4 +- code/AssetLib/STEPParser/STEPFileReader.cpp | 5 +- code/AssetLib/Step/STEPFile.h | 4 +- code/AssetLib/X/XFileExporter.cpp | 4 +- code/AssetLib/X3D/X3DExporter.hpp | 6 +- code/AssetLib/glTF/glTFAsset.h | 10 ++- code/AssetLib/glTF/glTFAsset.inl | 2 +- code/AssetLib/glTF/glTFExporter.cpp | 3 +- code/AssetLib/glTF2/glTF2Asset.h | 4 +- code/AssetLib/glTF2/glTF2Asset.inl | 2 +- code/AssetLib/glTF2/glTF2Exporter.cpp | 3 +- code/Pbrt/PbrtExporter.cpp | 23 +++--- code/Pbrt/PbrtExporter.h | 4 +- code/PostProcessing/EmbedTexturesProcess.cpp | 2 +- code/PostProcessing/EmbedTexturesProcess.h | 2 +- test/unit/utTypes.cpp | 4 +- 37 files changed, 131 insertions(+), 143 deletions(-) diff --git a/code/AssetLib/3DS/3DSHelper.h b/code/AssetLib/3DS/3DSHelper.h index 1930c0c40..e8efbf949 100644 --- a/code/AssetLib/3DS/3DSHelper.h +++ b/code/AssetLib/3DS/3DSHelper.h @@ -348,16 +348,16 @@ struct Texture { // empty } - Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(std::move(other.mTextureBlend)), + Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(other.mTextureBlend), mMapName(std::move(other.mMapName)), - mOffsetU(std::move(other.mOffsetU)), - mOffsetV(std::move(other.mOffsetV)), - mScaleU(std::move(other.mScaleU)), - mScaleV(std::move(other.mScaleV)), - mRotation(std::move(other.mRotation)), - mMapMode(std::move(other.mMapMode)), - bPrivate(std::move(other.bPrivate)), - iUVSrc(std::move(other.iUVSrc)) { + 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 } @@ -366,16 +366,16 @@ struct Texture { return *this; } - mTextureBlend = std::move(other.mTextureBlend); + mTextureBlend = other.mTextureBlend; mMapName = std::move(other.mMapName); - mOffsetU = std::move(other.mOffsetU); - mOffsetV = std::move(other.mOffsetV); - mScaleU = std::move(other.mScaleU); - mScaleV = std::move(other.mScaleV); - mRotation = std::move(other.mRotation); - mMapMode = std::move(other.mMapMode); - bPrivate = std::move(other.bPrivate); - iUVSrc = std::move(other.iUVSrc); + 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; } @@ -461,13 +461,13 @@ struct Material { //! 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(std::move(other.mDiffuse)), - mSpecularExponent(std::move(other.mSpecularExponent)), - mShininessStrength(std::move(other.mShininessStrength)), - mSpecular(std::move(other.mSpecular)), - mAmbient(std::move(other.mAmbient)), - mShading(std::move(other.mShading)), - mTransparency(std::move(other.mTransparency)), + 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)), @@ -475,10 +475,10 @@ struct Material { sTexBump(std::move(other.sTexBump)), sTexEmissive(std::move(other.sTexEmissive)), sTexShininess(std::move(other.sTexShininess)), - mBumpHeight(std::move(other.mBumpHeight)), - mEmissive(std::move(other.mEmissive)), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), sTexAmbient(std::move(other.sTexAmbient)), - mTwoSided(std::move(other.mTwoSided)) { + mTwoSided(other.mTwoSided) { // empty } @@ -488,13 +488,13 @@ struct Material { } mName = std::move(other.mName); - mDiffuse = std::move(other.mDiffuse); - mSpecularExponent = std::move(other.mSpecularExponent); - mShininessStrength = std::move(other.mShininessStrength), - mSpecular = std::move(other.mSpecular); - mAmbient = std::move(other.mAmbient); - mShading = std::move(other.mShading); - mTransparency = std::move(other.mTransparency); + 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); @@ -502,10 +502,10 @@ struct Material { sTexBump = std::move(other.sTexBump); sTexEmissive = std::move(other.sTexEmissive); sTexShininess = std::move(other.sTexShininess); - mBumpHeight = std::move(other.mBumpHeight); - mEmissive = std::move(other.mEmissive); + mBumpHeight = other.mBumpHeight; + mEmissive = other.mEmissive; sTexAmbient = std::move(other.sTexAmbient); - mTwoSided = std::move(other.mTwoSided); + mTwoSided = other.mTwoSided; return *this; } diff --git a/code/AssetLib/ASE/ASEParser.h b/code/AssetLib/ASE/ASEParser.h index d04fc0662..f49cfc36f 100644 --- a/code/AssetLib/ASE/ASEParser.h +++ b/code/AssetLib/ASE/ASEParser.h @@ -95,8 +95,8 @@ struct Material : public D3DS::Material { Material(Material &&other) AI_NO_EXCEPT : D3DS::Material(std::move(other)), avSubMaterials(std::move(other.avSubMaterials)), - pcInstance(std::move(other.pcInstance)), - bNeed(std::move(other.bNeed)) { + pcInstance(other.pcInstance), + bNeed(other.bNeed) { other.pcInstance = nullptr; } @@ -108,8 +108,8 @@ struct Material : public D3DS::Material { //D3DS::Material::operator=(std::move(other)); avSubMaterials = std::move(other.avSubMaterials); - pcInstance = std::move(other.pcInstance); - bNeed = std::move(other.bNeed); + pcInstance = other.pcInstance; + bNeed = other.bNeed; other.pcInstance = nullptr; diff --git a/code/AssetLib/B3D/B3DImporter.cpp b/code/AssetLib/B3D/B3DImporter.cpp index 3d9a5075a..11f7bcd14 100644 --- a/code/AssetLib/B3D/B3DImporter.cpp +++ b/code/AssetLib/B3D/B3DImporter.cpp @@ -143,7 +143,7 @@ AI_WONT_RETURN void B3DImporter::Oops() { } // ------------------------------------------------------------------------------------------------ -AI_WONT_RETURN void B3DImporter::Fail(string str) { +AI_WONT_RETURN void B3DImporter::Fail(const string &str) { #ifdef DEBUG_B3D ASSIMP_LOG_ERROR("Error in B3D file data: ", str); #endif diff --git a/code/AssetLib/B3D/B3DImporter.h b/code/AssetLib/B3D/B3DImporter.h index e2a75abdf..a7ed65c3b 100644 --- a/code/AssetLib/B3D/B3DImporter.h +++ b/code/AssetLib/B3D/B3DImporter.h @@ -96,7 +96,7 @@ private: }; AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX; - AI_WONT_RETURN void Fail( std::string str ) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Fail(const std::string &str) AI_WONT_RETURN_SUFFIX; void ReadTEXS(); void ReadBRUS(); diff --git a/code/AssetLib/Blender/BlenderLoader.cpp b/code/AssetLib/Blender/BlenderLoader.cpp index 7cf4e070e..42a7a1723 100644 --- a/code/AssetLib/Blender/BlenderLoader.cpp +++ b/code/AssetLib/Blender/BlenderLoader.cpp @@ -679,7 +679,7 @@ void BlenderImporter::BuildMaterials(ConversionData &conv_data) { BuildDefaultMaterial(conv_data); - for (std::shared_ptr mat : conv_data.materials_raw) { + for (const std::shared_ptr &mat : conv_data.materials_raw) { // reset per material global counters for (size_t i = 0; i < sizeof(conv_data.next_texture) / sizeof(conv_data.next_texture[0]); ++i) { diff --git a/code/AssetLib/COB/COBLoader.cpp b/code/AssetLib/COB/COBLoader.cpp index 94327c683..822bce16d 100644 --- a/code/AssetLib/COB/COBLoader.cpp +++ b/code/AssetLib/COB/COBLoader.cpp @@ -230,7 +230,7 @@ void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } // ------------------------------------------------------------------------------------------------ -void ConvertTexture(std::shared_ptr tex, aiMaterial *out, aiTextureType type) { +void ConvertTexture(const std::shared_ptr &tex, aiMaterial *out, aiTextureType type) { const aiString path(tex->path); out->AddProperty(&path, AI_MATKEY_TEXTURE(type, 0)); out->AddProperty(&tex->transform, 1, AI_MATKEY_UVTRANSFORM(type, 0)); diff --git a/code/AssetLib/Collada/ColladaParser.cpp b/code/AssetLib/Collada/ColladaParser.cpp index 5dbf0a567..3166136b2 100644 --- a/code/AssetLib/Collada/ColladaParser.cpp +++ b/code/AssetLib/Collada/ColladaParser.cpp @@ -453,7 +453,7 @@ void ColladaParser::PostProcessRootAnimations() { temp.mSubAnims.push_back(clip); - for (std::string animationID : it.second) { + for (const std::string &animationID : it.second) { AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID); if (animation != mAnimationLibrary.end()) { diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index a564b3e9b..a6a3fc566 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -2599,7 +2599,7 @@ void FBXConverter::ConvertAnimationStack(const AnimationStack &st) { anim->mMorphMeshChannels = new aiMeshMorphAnim *[numMorphMeshChannels]; anim->mNumMorphMeshChannels = numMorphMeshChannels; unsigned int i = 0; - for (auto morphAnimIt : morphAnimDatas) { + for (const auto &morphAnimIt : morphAnimDatas) { morphAnimData *animData = morphAnimIt.second; unsigned int numKeys = static_cast(animData->size()); aiMeshMorphAnim *meshMorphAnim = new aiMeshMorphAnim(); diff --git a/code/AssetLib/FBX/FBXDocument.cpp b/code/AssetLib/FBX/FBXDocument.cpp index 7adaadf6c..0c4435348 100644 --- a/code/AssetLib/FBX/FBXDocument.cpp +++ b/code/AssetLib/FBX/FBXDocument.cpp @@ -57,9 +57,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include #include #include +#include +#include namespace Assimp { namespace FBX { @@ -248,10 +249,8 @@ Object::~Object() } // ------------------------------------------------------------------------------------------------ -FileGlobalSettings::FileGlobalSettings(const Document& doc, std::shared_ptr props) -: props(props) -, doc(doc) -{ +FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptr props) : + props(std::move(props)), doc(doc) { // empty } diff --git a/code/AssetLib/FBX/FBXExportNode.cpp b/code/AssetLib/FBX/FBXExportNode.cpp index 91e421420..817308ec8 100644 --- a/code/AssetLib/FBX/FBXExportNode.cpp +++ b/code/AssetLib/FBX/FBXExportNode.cpp @@ -144,9 +144,8 @@ void FBX::Node::AddP70time( // public member functions for writing nodes to stream void FBX::Node::Dump( - std::shared_ptr outfile, - bool binary, int indent -) { + const std::shared_ptr &outfile, + bool binary, int indent) { if (binary) { Assimp::StreamWriterLE outstream(outfile); DumpBinary(outstream); diff --git a/code/AssetLib/FBX/FBXExportNode.h b/code/AssetLib/FBX/FBXExportNode.h index c5f29ef0f..6ef27972d 100644 --- a/code/AssetLib/FBX/FBXExportNode.h +++ b/code/AssetLib/FBX/FBXExportNode.h @@ -157,9 +157,8 @@ public: // member functions for writing data to a file or stream // write the full node to the given file or stream void Dump( - std::shared_ptr outfile, - bool binary, int indent - ); + const std::shared_ptr &outfile, + bool binary, int indent); void Dump(Assimp::StreamWriterLE &s, bool binary, int indent); // these other functions are for writing data piece by piece. diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index 6bdd0b5be..27b65a27f 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -2718,16 +2718,14 @@ void FBXExporter::WriteModelNodes( } } - void FBXExporter::WriteAnimationCurveNode( - StreamWriterLE& outstream, - int64_t uid, - const std::string& name, // "T", "R", or "S" - aiVector3D default_value, - std::string property_name, // "Lcl Translation" etc - int64_t layer_uid, - int64_t node_uid -) { + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t layer_uid, + int64_t node_uid) { FBX::Node n("AnimationCurveNode"); n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", ""); FBX::Node p("Properties70"); @@ -2742,7 +2740,6 @@ void FBXExporter::WriteAnimationCurveNode( this->connections.emplace_back("C", "OP", uid, node_uid, property_name); } - void FBXExporter::WriteAnimationCurve( StreamWriterLE& outstream, double default_value, diff --git a/code/AssetLib/FBX/FBXExporter.h b/code/AssetLib/FBX/FBXExporter.h index 563183268..3f5e8029d 100644 --- a/code/AssetLib/FBX/FBXExporter.h +++ b/code/AssetLib/FBX/FBXExporter.h @@ -156,14 +156,13 @@ namespace Assimp FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs ); void WriteAnimationCurveNode( - StreamWriterLE& outstream, - int64_t uid, - const std::string& name, // "T", "R", or "S" - aiVector3D default_value, - std::string property_name, // "Lcl Translation" etc - int64_t animation_layer_uid, - int64_t node_uid - ); + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t animation_layer_uid, + int64_t node_uid); void WriteAnimationCurve( StreamWriterLE& outstream, double default_value, diff --git a/code/AssetLib/FBX/FBXProperties.cpp b/code/AssetLib/FBX/FBXProperties.cpp index 1a5ebffd1..c3f4de260 100644 --- a/code/AssetLib/FBX/FBXProperties.cpp +++ b/code/AssetLib/FBX/FBXProperties.cpp @@ -52,6 +52,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXDocumentUtil.h" #include "FBXProperties.h" +#include + namespace Assimp { namespace FBX { @@ -172,10 +174,8 @@ PropertyTable::PropertyTable() } // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable(const Element& element, std::shared_ptr templateProps) -: templateProps(templateProps) -, element(&element) -{ +PropertyTable::PropertyTable(const Element &element, std::shared_ptr templateProps) : + templateProps(std::move(templateProps)), element(&element) { const Scope& scope = GetRequiredScope(element); for(const ElementMap::value_type& v : scope.Elements()) { if(v.first != "P") { @@ -199,7 +199,6 @@ PropertyTable::PropertyTable(const Element& element, std::shared_ptr meshtmp = std::make_shared(); if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { - for(std::shared_ptr shell :shellmod->SbsmBoundary) { + for (const std::shared_ptr &shell : shellmod->SbsmBoundary) { try { const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>(); const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); diff --git a/code/AssetLib/IFC/IFCMaterial.cpp b/code/AssetLib/IFC/IFCMaterial.cpp index 2a79f0754..c26a3aa0a 100644 --- a/code/AssetLib/IFC/IFCMaterial.cpp +++ b/code/AssetLib/IFC/IFCMaterial.cpp @@ -75,7 +75,7 @@ static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* mat->AddProperty(&name,AI_MATKEY_NAME); // now see which kinds of surface information are present - for(std::shared_ptr< const IFC::Schema_2x3::IfcSurfaceStyleElementSelect > sel2 : surf->Styles) { + for (const std::shared_ptr &sel2 : surf->Styles) { if (const IFC::Schema_2x3::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr(conv.db)) { aiColor4D col_base,col; @@ -124,7 +124,7 @@ static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* } } } - } + } } } @@ -134,7 +134,7 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat for(;range.first != range.second; ++range.first) { if(const IFC::Schema_2x3::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr()) { for(const IFC::Schema_2x3::IfcPresentationStyleAssignment& as : styled->Styles) { - for(std::shared_ptr sel : as.Styles) { + for (const std::shared_ptr &sel : as.Styles) { if( const IFC::Schema_2x3::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr(conv.db) ) { // try to satisfy from cache diff --git a/code/AssetLib/IFC/IFCUtil.h b/code/AssetLib/IFC/IFCUtil.h index a1190746b..20b9a617c 100644 --- a/code/AssetLib/IFC/IFCUtil.h +++ b/code/AssetLib/IFC/IFCUtil.h @@ -54,6 +54,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + +#include + struct aiNode; namespace Assimp { @@ -137,14 +141,10 @@ struct TempOpening } // ------------------------------------------------------------------------------ - TempOpening(const IFC::Schema_2x3::IfcSolidModel* solid,IfcVector3 extrusionDir, - std::shared_ptr profileMesh, - std::shared_ptr profileMesh2D) - : solid(solid) - , extrusionDir(extrusionDir) - , profileMesh(profileMesh) - , profileMesh2D(profileMesh2D) - { + TempOpening(const IFC::Schema_2x3::IfcSolidModel *solid, IfcVector3 extrusionDir, + std::shared_ptr profileMesh, + std::shared_ptr profileMesh2D) : + solid(solid), extrusionDir(extrusionDir), profileMesh(std::move(std::move(profileMesh))), profileMesh2D(std::move(std::move(profileMesh2D))) { } // ------------------------------------------------------------------------------ diff --git a/code/AssetLib/LWS/LWSLoader.cpp b/code/AssetLib/LWS/LWSLoader.cpp index d469a1064..01a50b6e4 100644 --- a/code/AssetLib/LWS/LWSLoader.cpp +++ b/code/AssetLib/LWS/LWSLoader.cpp @@ -318,7 +318,7 @@ void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { } else { ++s; } - std::string::size_type t = src.path.substr(s).find_last_of("."); + 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); return; diff --git a/code/AssetLib/M3D/M3DImporter.cpp b/code/AssetLib/M3D/M3DImporter.cpp index 38bbd1d4a..4b2f9bd77 100644 --- a/code/AssetLib/M3D/M3DImporter.cpp +++ b/code/AssetLib/M3D/M3DImporter.cpp @@ -655,7 +655,7 @@ void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned in // ------------------------------------------------------------------------------------------------ // find a node by name -aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) { +aiNode *M3DImporter::findNode(aiNode *pNode, const aiString &name) { ai_assert(pNode != nullptr); ai_assert(mScene != nullptr); diff --git a/code/AssetLib/M3D/M3DImporter.h b/code/AssetLib/M3D/M3DImporter.h index 7a2a9fbd3..05e8ced7b 100644 --- a/code/AssetLib/M3D/M3DImporter.h +++ b/code/AssetLib/M3D/M3DImporter.h @@ -89,8 +89,8 @@ private: // helper functions aiColor4D mkColor(uint32_t c); void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); - aiNode *findNode(aiNode *pNode, aiString name); - void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + aiNode *findNode(aiNode *pNode, const aiString &name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector *faces, std::vector *verteces, std::vector *normals, std::vector *texcoords, std::vector *colors, std::vector *vertexids); diff --git a/code/AssetLib/Ogre/OgreMaterial.cpp b/code/AssetLib/Ogre/OgreMaterial.cpp index 295dedde6..6da82f89c 100644 --- a/code/AssetLib/Ogre/OgreMaterial.cpp +++ b/code/AssetLib/Ogre/OgreMaterial.cpp @@ -415,8 +415,8 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr // User defined Assimp config property to detect texture type from filename. if (m_detectTextureTypeFromFilename) { - size_t posSuffix = textureRef.find_last_of("."); - size_t posUnderscore = textureRef.find_last_of("_"); + size_t posSuffix = textureRef.find_last_of('.'); + size_t posUnderscore = textureRef.find_last_of('_'); if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) { string identifier = ai_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); diff --git a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp index becfa41fc..a1da8fd74 100644 --- a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp +++ b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp @@ -99,7 +99,7 @@ static void extractIds(const std::string &key, int &id1, int &id2) { return; } - const std::string::size_type pos = key.find("."); + const std::string::size_type pos = key.find('.'); if (std::string::npos == pos) { return; } @@ -208,7 +208,7 @@ void Q3BSPFileImporter::separateMapName(const std::string &importName, std::stri return; } - const std::string::size_type pos = importName.rfind(","); + const std::string::size_type pos = importName.rfind(','); if (std::string::npos == pos) { archiveName = importName; return; diff --git a/code/AssetLib/STEPParser/STEPFileReader.cpp b/code/AssetLib/STEPParser/STEPFileReader.cpp index e97ea1e28..ac6d83672 100644 --- a/code/AssetLib/STEPParser/STEPFileReader.cpp +++ b/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -49,8 +49,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "STEPFileEncoding.h" #include #include -#include #include +#include +#include using namespace Assimp; @@ -87,7 +88,7 @@ static const char *ISO_Token = "ISO-10303-21;"; static const char *FILE_SCHEMA_Token = "FILE_SCHEMA"; // ------------------------------------------------------------------------------------------------ STEP::DB* STEP::ReadFileHeader(std::shared_ptr stream) { - std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); + std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(std::move(stream))); std::unique_ptr db = std::unique_ptr(new STEP::DB(reader)); LineSplitter &splitter = db->GetSplitter(); diff --git a/code/AssetLib/Step/STEPFile.h b/code/AssetLib/Step/STEPFile.h index 90eaef5f3..e09faad98 100644 --- a/code/AssetLib/Step/STEPFile.h +++ b/code/AssetLib/Step/STEPFile.h @@ -634,7 +634,7 @@ private: }; template -inline bool operator==(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 } @@ -816,7 +816,7 @@ public: typedef std::pair RefMapRange; private: - DB(std::shared_ptr reader) : + DB(const std::shared_ptr &reader) : reader(reader), splitter(*reader, true, true), evaluated_count(), schema(nullptr) {} public: diff --git a/code/AssetLib/X/XFileExporter.cpp b/code/AssetLib/X/XFileExporter.cpp index da20b935a..bd997a3c5 100644 --- a/code/AssetLib/X/XFileExporter.cpp +++ b/code/AssetLib/X/XFileExporter.cpp @@ -530,8 +530,8 @@ void XFileExporter::writePath(const aiString &path) while( str.find( "\\\\") != std::string::npos) str.replace( str.find( "\\\\"), 2, "\\"); - while( str.find( "\\") != std::string::npos) - str.replace( str.find( "\\"), 1, "/"); + while (str.find('\\') != std::string::npos) + str.replace(str.find('\\'), 1, "/"); mOutput << str; diff --git a/code/AssetLib/X3D/X3DExporter.hpp b/code/AssetLib/X3D/X3DExporter.hpp index 00115e0b6..fc8f9a921 100644 --- a/code/AssetLib/X3D/X3DExporter.hpp +++ b/code/AssetLib/X3D/X3DExporter.hpp @@ -63,9 +63,9 @@ class X3DExporter { // empty } - SAttribute(SAttribute && rhs) : - Name(std::move(rhs.Name)), - Value(std::move(rhs.Value)) { + SAttribute(SAttribute &&rhs) noexcept : + Name(rhs.Name), + Value(rhs.Value) { // empty } }; diff --git a/code/AssetLib/glTF/glTFAsset.h b/code/AssetLib/glTF/glTFAsset.h index da49a1737..4cef646d2 100644 --- a/code/AssetLib/glTF/glTFAsset.h +++ b/code/AssetLib/glTF/glTFAsset.h @@ -456,11 +456,10 @@ namespace glTF /// \param [in] pDecodedData - pointer to decoded data array. /// \param [in] pDecodedData_Length - size of encoded region, in bytes. /// \param [in] pID - ID of the region. - SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) - : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) - {} + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : + Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) {} - /// \fn ~SEncodedRegion() + /// \fn ~SEncodedRegion() /// Destructor. ~SEncodedRegion() { delete [] DecodedData; } }; @@ -1149,8 +1148,7 @@ namespace glTF void ReadExtensionsUsed(Document& doc); - - IOStream* OpenFile(std::string path, const char* mode, bool absolute = false); + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); }; } diff --git a/code/AssetLib/glTF/glTFAsset.inl b/code/AssetLib/glTF/glTFAsset.inl index 6e1e60846..e915a3aee 100644 --- a/code/AssetLib/glTF/glTFAsset.inl +++ b/code/AssetLib/glTF/glTFAsset.inl @@ -1377,7 +1377,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(std::string path, const char *mode, bool absolute) { +inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool absolute) { #ifdef ASSIMP_API (void)absolute; return mIOSystem->Open(path, mode); diff --git a/code/AssetLib/glTF/glTFExporter.cpp b/code/AssetLib/glTF/glTFExporter.cpp index 5e1992319..810263f52 100644 --- a/code/AssetLib/glTF/glTFExporter.cpp +++ b/code/AssetLib/glTF/glTFExporter.cpp @@ -405,8 +405,7 @@ void glTFExporter::ExportMaterials() * Search through node hierarchy and find the node containing the given meshID. * Returns true on success, and false otherwise. */ -bool FindMeshNode(Ref& nodeIn, Ref& meshNode, std::string meshID) -{ +bool FindMeshNode(Ref &nodeIn, Ref &meshNode, const std::string &meshID) { for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { if (meshID.compare(nodeIn->meshes[i]->id) == 0) { meshNode = nodeIn; diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 6aa0f92ed..eaacf8564 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -408,7 +408,7 @@ public: /// \param [in] pDecodedData - pointer to decoded data array. /// \param [in] pDecodedData_Length - size of encoded region, in bytes. /// \param [in] pID - ID of the region. - SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string pID) : + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), @@ -1188,7 +1188,7 @@ private: void ReadExtensionsUsed(Document &doc); void ReadExtensionsRequired(Document &doc); - IOStream *OpenFile(std::string path, const char *mode, bool absolute = false); + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); }; inline std::string getContextForErrorMessages(const std::string &id, const std::string &name) { diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index b51ac20c2..ab81e3519 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -2132,7 +2132,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(std::string path, const char *mode, bool /*absolute*/) { +inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool /*absolute*/) { #ifdef ASSIMP_API return mIOSystem->Open(path, mode); #else diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 83356f7c2..0e7251b1e 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -798,8 +798,7 @@ void glTF2Exporter::ExportMaterials() * Search through node hierarchy and find the node containing the given meshID. * Returns true on success, and false otherwise. */ -bool FindMeshNode(Ref& nodeIn, Ref& meshNode, std::string meshID) -{ +bool FindMeshNode(Ref &nodeIn, Ref &meshNode, const std::string &meshID) { for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { if (meshID.compare(nodeIn->meshes[i]->id) == 0) { meshNode = nodeIn; diff --git a/code/Pbrt/PbrtExporter.cpp b/code/Pbrt/PbrtExporter.cpp index b793c37f9..0be244600 100644 --- a/code/Pbrt/PbrtExporter.cpp +++ b/code/Pbrt/PbrtExporter.cpp @@ -106,14 +106,13 @@ void ExportScenePbrt ( } // end of namespace Assimp // Constructor -PbrtExporter::PbrtExporter ( - const aiScene* pScene, IOSystem* pIOSystem, - const std::string path, const std::string file) -: mScene(pScene), - mIOSystem(pIOSystem), - mPath(path), - mFile(file) -{ +PbrtExporter::PbrtExporter( + const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file) : + mScene(pScene), + mIOSystem(pIOSystem), + mPath(path), + mFile(file) { // Export embedded textures. if (mScene->mNumTextures > 0) if (!mIOSystem->CreateDirectory("textures")) @@ -210,12 +209,12 @@ void PbrtExporter::WriteMetaData() { aiString* value = static_cast(pMetaData->mValues[i].mData); std::string svalue = value->C_Str(); - std::size_t found = svalue.find_first_of("\n"); + std::size_t found = svalue.find_first_of('\n'); mOutput << "\n"; while (found != std::string::npos) { mOutput << "# " << svalue.substr(0, found) << "\n"; svalue = svalue.substr(found + 1); - found = svalue.find_first_of("\n"); + found = svalue.find_first_of('\n'); } mOutput << "# " << svalue << "\n"; break; @@ -596,8 +595,8 @@ void PbrtExporter::WriteMaterial(int m) { } mOutput << "\n"; - auto White = [](aiColor3D c) { return c.r == 1 && c.g == 1 && c.b == 1; }; - auto Black = [](aiColor3D c) { return c.r == 0 && c.g == 0 && c.b == 0; }; + auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; }; + auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; }; aiColor3D diffuse, specular, transparency; bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS && diff --git a/code/Pbrt/PbrtExporter.h b/code/Pbrt/PbrtExporter.h index 167f318fc..e8ff03ccb 100644 --- a/code/Pbrt/PbrtExporter.h +++ b/code/Pbrt/PbrtExporter.h @@ -74,8 +74,8 @@ class PbrtExporter { public: /// Constructor for a specific scene to export - PbrtExporter(const aiScene* pScene, IOSystem* pIOSystem, - const std::string path, const std::string file); + PbrtExporter(const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file); /// Destructor virtual ~PbrtExporter(); diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index cb2853926..500032c39 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -100,7 +100,7 @@ void EmbedTexturesProcess::Execute(aiScene* pScene) { ASSIMP_LOG_INFO("EmbedTexturesProcess finished. Embedded ", embeddedTexturesCount, " textures." ); } -bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const { +bool EmbedTexturesProcess::addTexture(aiScene *pScene, const std::string &path) const { std::streampos imageSize = 0; std::string imagePath = path; diff --git a/code/PostProcessing/EmbedTexturesProcess.h b/code/PostProcessing/EmbedTexturesProcess.h index 5915e0d44..b33968850 100644 --- a/code/PostProcessing/EmbedTexturesProcess.h +++ b/code/PostProcessing/EmbedTexturesProcess.h @@ -78,7 +78,7 @@ public: private: // Resolve the path and add the file content to the scene as a texture. - bool addTexture(aiScene* pScene, std::string path) const; + bool addTexture(aiScene *pScene, const std::string &path) const; private: std::string mRootPath; diff --git a/test/unit/utTypes.cpp b/test/unit/utTypes.cpp index cc354eb3d..1ac9a1d5e 100644 --- a/test/unit/utTypes.cpp +++ b/test/unit/utTypes.cpp @@ -53,8 +53,8 @@ class utTypes : public ::testing::Test { TEST_F( utTypes, Color3dCpmpareOpTest ) { aiColor3D col1( 1, 2, 3 ); aiColor3D col2( 4, 5, 6 ); - aiColor3D col3( col1 ); - + const aiColor3D &col3(col1); + EXPECT_FALSE( col1 == col2 ); EXPECT_FALSE( col2 == col3 ); EXPECT_TRUE( col1 == col3 ); From 16508687015f383b423d710a2a1b13523c2fd9f9 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 22 Jun 2021 12:32:58 -0400 Subject: [PATCH 33/44] Remove redundant include --- code/AssetLib/IFC/IFCUtil.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/AssetLib/IFC/IFCUtil.h b/code/AssetLib/IFC/IFCUtil.h index 20b9a617c..e1dd064e1 100644 --- a/code/AssetLib/IFC/IFCUtil.h +++ b/code/AssetLib/IFC/IFCUtil.h @@ -56,8 +56,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include - struct aiNode; namespace Assimp { From b17c2f29e9472e0d345a22f831562fc23ba703fd Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 22 Jun 2021 12:44:36 -0400 Subject: [PATCH 34/44] Replace noexcept with proper macro --- code/AssetLib/X3D/X3DExporter.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/X3D/X3DExporter.hpp b/code/AssetLib/X3D/X3DExporter.hpp index fc8f9a921..fefaba9f3 100644 --- a/code/AssetLib/X3D/X3DExporter.hpp +++ b/code/AssetLib/X3D/X3DExporter.hpp @@ -63,7 +63,7 @@ class X3DExporter { // empty } - SAttribute(SAttribute &&rhs) noexcept : + SAttribute(SAttribute &&rhs) AI_NO_EXCEPT : Name(rhs.Name), Value(rhs.Value) { // empty From 36815b014b7c55e4a5a9a6677a7565dc392aa4ca Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 22 Jun 2021 20:05:16 +0200 Subject: [PATCH 35/44] Update FBXExporter.h --- code/AssetLib/FBX/FBXExporter.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/AssetLib/FBX/FBXExporter.h b/code/AssetLib/FBX/FBXExporter.h index 563183268..2b751ea26 100644 --- a/code/AssetLib/FBX/FBXExporter.h +++ b/code/AssetLib/FBX/FBXExporter.h @@ -64,10 +64,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiScene; struct aiNode; struct aiLight; -//struct aiMaterial; -namespace Assimp -{ +namespace Assimp { class IOSystem; class IOStream; class ExportProperties; From 544148a6263d6141acf157fb23d603583bb6b2ea Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 22 Jun 2021 14:28:46 -0400 Subject: [PATCH 36/44] Fix code insertion duplication --- code/AssetLib/IFC/IFCUtil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/IFC/IFCUtil.h b/code/AssetLib/IFC/IFCUtil.h index e1dd064e1..b18f35052 100644 --- a/code/AssetLib/IFC/IFCUtil.h +++ b/code/AssetLib/IFC/IFCUtil.h @@ -142,7 +142,7 @@ struct TempOpening TempOpening(const IFC::Schema_2x3::IfcSolidModel *solid, IfcVector3 extrusionDir, std::shared_ptr profileMesh, std::shared_ptr profileMesh2D) : - solid(solid), extrusionDir(extrusionDir), profileMesh(std::move(std::move(profileMesh))), profileMesh2D(std::move(std::move(profileMesh2D))) { + solid(solid), extrusionDir(extrusionDir), profileMesh(std::move(profileMesh)), profileMesh2D(std::move(profileMesh2D)) { } // ------------------------------------------------------------------------------ From 6170c49155b0e9f7678ab1ceb3cb96c720b7536c Mon Sep 17 00:00:00 2001 From: Pankaj Tyagi Date: Wed, 23 Jun 2021 16:49:09 +0530 Subject: [PATCH 37/44] Fixed: 1. FBX import is unable to read the texture UV rotation angle. 2. FBX export is unable to write the texture UV rotation angle. --- code/AssetLib/FBX/FBXConverter.cpp | 3 +++ code/AssetLib/FBX/FBXDocument.h | 5 +++++ code/AssetLib/FBX/FBXExporter.cpp | 7 +++++++ code/AssetLib/FBX/FBXMaterial.cpp | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index a564b3e9b..066b79871 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -1766,6 +1766,7 @@ void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const TextureMap // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0); const PropertyTable &props = tex->Props(); @@ -1885,6 +1886,7 @@ void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const LayeredTex // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex); const PropertyTable &props = tex->Props(); @@ -2324,6 +2326,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial *out_mat, const PropertyTa // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, (name + "|uvtrafo").c_str(), aiTextureType_UNKNOWN, 0); int uvIndex = 0; diff --git a/code/AssetLib/FBX/FBXDocument.h b/code/AssetLib/FBX/FBXDocument.h index 69cda1c1a..1ee526368 100644 --- a/code/AssetLib/FBX/FBXDocument.h +++ b/code/AssetLib/FBX/FBXDocument.h @@ -500,6 +500,10 @@ public: return uvScaling; } + const ai_real &UVRotation() const { + return uvRotation; + } + const PropertyTable& Props() const { ai_assert(props.get()); return *props.get(); @@ -517,6 +521,7 @@ public: private: aiVector2D uvTrans; aiVector2D uvScaling; + ai_real uvRotation; std::string type; std::string relativeFileName; diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index 6bdd0b5be..486e08da9 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -1688,6 +1688,10 @@ void FBXExporter::WriteObjects () // link the image data to the texture connections.emplace_back("C", "OO", image_uid, texture_uid); + aiUVTransform trafo; + unsigned int max = sizeof(aiUVTransform); + aiGetMaterialFloatArray(mat, AI_MATKEY_UVTRANSFORM(aiTextureType_DIFFUSE, 0), (float *)&trafo, &max); + // now write the actual texture node FBX::Node tnode("Texture"); // TODO: some way to determine texture name? @@ -1698,6 +1702,9 @@ void FBXExporter::WriteObjects () tnode.AddChild("Version", int32_t(202)); tnode.AddChild("TextureName", texture_name); FBX::Node p("Properties70"); + p.AddP70vectorA("Translation", trafo.mTranslation[0], trafo.mTranslation[1], 0.0); + p.AddP70vectorA("Rotation", 0, 0, trafo.mRotation); + 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); diff --git a/code/AssetLib/FBX/FBXMaterial.cpp b/code/AssetLib/FBX/FBXMaterial.cpp index 6ada9630b..aaa043c12 100644 --- a/code/AssetLib/FBX/FBXMaterial.cpp +++ b/code/AssetLib/FBX/FBXMaterial.cpp @@ -210,6 +210,11 @@ Texture::Texture(uint64_t id, const Element& element, const Document& doc, const uvTrans.y = trans.y; } + const aiVector3D &rotation = PropertyGet(*props, "Rotation", ok); + if (ok) { + uvRotation = rotation.z; + } + // resolve video links if(doc.Settings().readTextures) { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID()); From 170063643cfaf86514b2d28d0c2d1ceae7df4791 Mon Sep 17 00:00:00 2001 From: Jerome St-Louis Date: Wed, 23 Jun 2021 18:11:52 -0400 Subject: [PATCH 38/44] include/material.h: Fixed broken C support - The aiGetMaterialFloat() and aiGetMaterialInteger() C preprocessor definitions were broken because: - They had a space before the opening parenthesis - Using material key definitions expanding 1 to argument into 3 breaks the invocation of macros expecting 5 parameters --- include/assimp/material.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/assimp/material.h b/include/assimp/material.h index 2024be07f..9b0c92b0d 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -1499,7 +1499,7 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialFloatArray( ai_real *pOut, unsigned int *pMax); -#ifdef __cplusplus +#if 1 //def __cplusplus // --------------------------------------------------------------------------- /** @brief Retrieve a single float property with a specific key from the material. @@ -1520,7 +1520,7 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialFloatArray( * @return Specifies whether the key has been found. If not, the output * float remains unmodified.*/ // --------------------------------------------------------------------------- -inline aiReturn aiGetMaterialFloat(const aiMaterial *pMat, +inline aiReturn aiGetMaterialFloat(const C_STRUCT aiMaterial *pMat, const char *pKey, unsigned int type, unsigned int index, @@ -1530,8 +1530,8 @@ inline aiReturn aiGetMaterialFloat(const aiMaterial *pMat, #else -// Use our friend, the C preprocessor -#define aiGetMaterialFloat (pMat, type, index, pKey, pOut) \ +// Use our friend, the C preprocessor // The macro does not work with e.g. AI_MATKEY_OPACITY expanding into 3 args +#define aiGetMaterialFloat(pMat, type, index, pKey, pOut) \ aiGetMaterialFloatArray(pMat, type, index, pKey, pOut, NULL) #endif //!__cplusplus @@ -1548,7 +1548,7 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialIntegerArray(const C_STRUCT aiMaterial * int *pOut, unsigned int *pMax); -#ifdef __cplusplus +#if 1 //def __cplusplus // --------------------------------------------------------------------------- /** @brief Retrieve an integer property with a specific key from a material @@ -1566,7 +1566,7 @@ inline aiReturn aiGetMaterialInteger(const C_STRUCT aiMaterial *pMat, #else // use our friend, the C preprocessor -#define aiGetMaterialInteger (pMat, type, index, pKey, pOut) \ +#define aiGetMaterialInteger(pMat, type, index, pKey, pOut) \ aiGetMaterialIntegerArray(pMat, type, index, pKey, pOut, NULL) #endif //!__cplusplus From 00bf7576883d7cf3edfffe51390855d293848a4e Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 24 Jun 2021 13:28:49 +0200 Subject: [PATCH 39/44] Update material.h --- include/assimp/material.h | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/include/assimp/material.h b/include/assimp/material.h index 9b0c92b0d..f0207c6de 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -1499,8 +1499,6 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialFloatArray( ai_real *pOut, unsigned int *pMax); -#if 1 //def __cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve a single float property with a specific key from the material. * @@ -1528,14 +1526,6 @@ inline aiReturn aiGetMaterialFloat(const C_STRUCT aiMaterial *pMat, return aiGetMaterialFloatArray(pMat, pKey, type, index, pOut, (unsigned int *)0x0); } -#else - -// Use our friend, the C preprocessor // The macro does not work with e.g. AI_MATKEY_OPACITY expanding into 3 args -#define aiGetMaterialFloat(pMat, type, index, pKey, pOut) \ - aiGetMaterialFloatArray(pMat, type, index, pKey, pOut, NULL) - -#endif //!__cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve an array of integer values with a specific key * from a material @@ -1548,8 +1538,6 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialIntegerArray(const C_STRUCT aiMaterial * int *pOut, unsigned int *pMax); -#if 1 //def __cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve an integer property with a specific key from a material * @@ -1563,14 +1551,6 @@ inline aiReturn aiGetMaterialInteger(const C_STRUCT aiMaterial *pMat, return aiGetMaterialIntegerArray(pMat, pKey, type, index, pOut, (unsigned int *)0x0); } -#else - -// use our friend, the C preprocessor -#define aiGetMaterialInteger(pMat, type, index, pKey, pOut) \ - aiGetMaterialIntegerArray(pMat, type, index, pKey, pOut, NULL) - -#endif //!__cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve a color value from the material property table * From d18d83881265714f0ef3d859449e0e5a44450b43 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 24 Jun 2021 16:18:11 +0200 Subject: [PATCH 40/44] Fix formatting --- code/AssetLib/glTF2/glTF2Asset.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 25e917712..605cda332 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -388,16 +388,18 @@ 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) - { + + ~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 } }; From b38b65ff49bf9bb061b2dcec79f18266bc680f4d Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 24 Jun 2021 17:03:00 +0200 Subject: [PATCH 41/44] Add const --- code/AssetLib/glTF2/glTF2Asset.inl | 49 ++++++++++++++++-------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 06a8b5c58..4bcfea5f7 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -777,12 +776,13 @@ inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncod } inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { - if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) { + return; + } for (SEncodedRegion *reg : EncodedRegion_List) { if (reg->ID == pID) { EncodedRegion_Current = reg; - return; } } @@ -832,9 +832,10 @@ inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const siz } inline size_t Buffer::AppendData(uint8_t *data, size_t length) { - size_t offset = this->byteLength; + const size_t offset = this->byteLength; + // Force alignment to 4 bits - size_t paddedLength = (length + 3) & ~3; + const size_t paddedLength = (length + 3) & ~3; Grow(paddedLength); memcpy(mData.get() + offset, data, length); memset(mData.get() + offset + length, 0, paddedLength - length); @@ -866,9 +867,7 @@ inline void Buffer::Grow(size_t amount) { // // struct BufferView // - inline void BufferView::Read(Value &obj, Asset &r) { - if (Value *bufferVal = FindUInt(obj, "buffer")) { buffer = r.buffers.Retrieve(bufferVal->GetUint()); } @@ -888,16 +887,21 @@ inline void BufferView::Read(Value &obj, Asset &r) { } inline uint8_t *BufferView::GetPointer(size_t accOffset) { - if (!buffer) return nullptr; + if (!buffer) { + return nullptr; + } uint8_t *basePtr = buffer->GetPointer(); - if (!basePtr) return nullptr; + if (!basePtr) { + return nullptr; + } size_t offset = accOffset + byteOffset; if (buffer->EncodedRegion_Current != nullptr) { const size_t begin = buffer->EncodedRegion_Current->Offset; const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length; - if ((offset >= begin) && (offset < end)) + if ((offset >= begin) && (offset < end)) { return &buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } } return basePtr + offset; @@ -923,18 +927,18 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) { while (pIndices != indicesEnd) { size_t offset; switch (indicesType) { - case ComponentType_UNSIGNED_BYTE: - offset = *pIndices; - break; - case ComponentType_UNSIGNED_SHORT: - offset = *reinterpret_cast(pIndices); - break; - case ComponentType_UNSIGNED_INT: - offset = *reinterpret_cast(pIndices); - break; - default: - // have fun with float and negative values from signed types as indices. - throw DeadlyImportError("Unsupported component type in index."); + case ComponentType_UNSIGNED_BYTE: + offset = *pIndices; + break; + case ComponentType_UNSIGNED_SHORT: + offset = *reinterpret_cast(pIndices); + break; + case ComponentType_UNSIGNED_INT: + offset = *reinterpret_cast(pIndices); + break; + default: + // have fun with float and negative values from signed types as indices. + throw DeadlyImportError("Unsupported component type in index."); } offset *= elementSize; @@ -946,7 +950,6 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) { } inline void Accessor::Read(Value &obj, Asset &r) { - if (Value *bufferViewVal = FindUInt(obj, "bufferView")) { bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint()); } From 0e41efb05008c94ed256c887e73b9f15a0abb872 Mon Sep 17 00:00:00 2001 From: Jerome St-Louis Date: Thu, 24 Jun 2021 13:25:44 -0400 Subject: [PATCH 42/44] glTF2: Improved support for AI_MATKEY_OPACITY - Exporter: Writing opacity to pbrMetallicRoughness.baseColorFactor[3] even when alphaMode is set - Importer: Setting AI_MATKEY_OPACITY from pbrMetallicRoughness.baseColorFactor[3] --- code/AssetLib/glTF2/glTF2Exporter.cpp | 16 +++++++--------- code/AssetLib/glTF2/glTF2Importer.cpp | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 3ed27a035..d45e04a5d 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -778,19 +778,17 @@ void glTF2Exporter::ExportMaterials() mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided); mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + float opacity; aiString alphaMode; + if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + if (opacity < 1) { + m->alphaMode = "BLEND"; + m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; + } + } if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { m->alphaMode = alphaMode.C_Str(); - } else { - float opacity; - - if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { - if (opacity < 1) { - m->alphaMode = "BLEND"; - m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; - } - } } { diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index d2fb1e9b8..1a04ed8b0 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -267,6 +267,7 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + aimat->AddProperty(&mat.pbrMetallicRoughness.baseColorFactor[3], 1, AI_MATKEY_OPACITY); aiString alphaMode(mat.alphaMode); aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); From 7c822f23bd075d3621d2e2f33188e1659f657b31 Mon Sep 17 00:00:00 2001 From: Promit Roy Date: Sun, 27 Jun 2021 00:53:40 -0400 Subject: [PATCH 43/44] Added support for custom properties ("extras") in glTF2 --- code/AssetLib/glTF2/glTF2Asset.h | 2 ++ code/AssetLib/glTF2/glTF2Asset.inl | 13 ++++++++++--- code/AssetLib/glTF2/glTF2Importer.cpp | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 605cda332..9824be149 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -411,6 +411,7 @@ struct Object { std::string name; //!< The user-defined name of this object CustomExtension customExtensions; + CustomExtension extras; //! Objects marked as special are not exported (used to emulate the binary body buffer) virtual bool IsSpecial() const { return false; } @@ -428,6 +429,7 @@ struct Object { inline Value *FindExtension(Value &val, const char *extensionId); inline void ReadExtensions(Value &val); + inline void ReadExtras(Value &val); }; // diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 4bcfea5f7..7bfaa81f5 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -372,6 +372,12 @@ inline void Object::ReadExtensions(Value &val) { } } +inline void Object::ReadExtras(Value &val) { + if (Value *curExtras = FindObject(val, "extras")) { + this->extras = glTF2::ReadExtensions("extras", *curExtras); + } +} + #ifdef ASSIMP_ENABLE_DRACO template @@ -612,6 +618,7 @@ Ref LazyDict::Retrieve(unsigned int i) { ReadMember(obj, "name", inst->name); inst->Read(obj, mAsset); inst->ReadExtensions(obj); + inst->ReadExtras(obj); Ref result = Add(inst.release()); mRecursiveReferenceCheck.erase(i); @@ -1661,9 +1668,9 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { } } - Value *extras = FindObject(pJSON_Object, "extras"); - if (nullptr != extras) { - if (Value *curTargetNames = FindArray(*extras, "targetNames")) { + Value *curExtras = FindObject(pJSON_Object, "extras"); + if (nullptr != curExtras) { + if (Value *curTargetNames = FindArray(*curExtras, "targetNames")) { this->targetNames.resize(curTargetNames->Size()); for (unsigned int i = 0; i < curTargetNames->Size(); ++i) { Value &targetNameValue = (*curTargetNames)[i]; diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index d2fb1e9b8..4827a9060 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -998,6 +998,14 @@ void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) { } } +void ParseExtras(aiMetadata *metadata, const CustomExtension &extension) { + if (extension.mValues.isPresent) { + for (size_t i = 0; i < extension.mValues.value.size(); ++i) { + ParseExtensions(metadata, extension.mValues.value[i]); + } + } +} + aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { Node &node = *ptr; @@ -1016,9 +1024,14 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & } } - if (node.customExtensions) { + if (node.customExtensions || node.extras) { ainode->mMetaData = new aiMetadata; - ParseExtensions(ainode->mMetaData, node.customExtensions); + if (node.customExtensions) { + ParseExtensions(ainode->mMetaData, node.customExtensions); + } + if (node.extras) { + ParseExtras(ainode->mMetaData, node.extras); + } } GetNodeTransform(ainode->mTransformation, node); From 0a48a35382236dd46f7a1decd8c57480edbd92e5 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 29 Jun 2021 21:22:22 +0200 Subject: [PATCH 44/44] closes https://github.com/assimp/assimp/issues/3971: fix wrong dependency --- tools/assimp_view/AnimEvaluator.cpp | 5 +++-- tools/assimp_view/AnimEvaluator.h | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/tools/assimp_view/AnimEvaluator.cpp b/tools/assimp_view/AnimEvaluator.cpp index df5167923..5a2ddc182 100644 --- a/tools/assimp_view/AnimEvaluator.cpp +++ b/tools/assimp_view/AnimEvaluator.cpp @@ -39,9 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -#include "assimp_view.h" +#include "AnimEvaluator.h" -#include +#include +#include using namespace AssimpView; diff --git a/tools/assimp_view/AnimEvaluator.h b/tools/assimp_view/AnimEvaluator.h index 950763081..566247604 100644 --- a/tools/assimp_view/AnimEvaluator.h +++ b/tools/assimp_view/AnimEvaluator.h @@ -1,4 +1,3 @@ -/** Calculates a pose for a given time of an animation */ /* --------------------------------------------------------------------------- Open Asset Import Library (assimp) @@ -40,11 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ +#pragma once #ifndef AV_ANIMEVALUATOR_H_INCLUDED #define AV_ANIMEVALUATOR_H_INCLUDED +/** Calculates a pose for a given time of an animation */ + #include #include +#include + +struct aiAnimation; namespace AssimpView { @@ -63,18 +68,19 @@ public: /// @brief The class destructor. ~AnimEvaluator(); - /** Evaluates the animation tracks for a given time stamp. The calculated pose can be retrieved as a - * array of transformation matrices afterwards by calling GetTransformations(). - * @param pTime The time for which you want to evaluate the animation, in seconds. Will be mapped into the animation cycle, so - * it can be an arbitrary value. Best use with ever-increasing time stamps. - */ + /// @brief Evaluates the animation tracks for a given time stamp. + /// The calculated pose can be retrieved as an array of transformation + /// matrices afterwards by calling GetTransformations(). + /// @param pTime The time for which you want to evaluate the animation, in seconds. + /// Will be mapped into the animation cycle, so it can get an arbitrary + /// value. Best use with ever-increasing time stamps. void Evaluate(double pTime); - /** Returns the transform matrices calculated at the last Evaluate() call. The array matches the mChannels array of - * the aiAnimation. */ + /// @brief Returns the transform matrices calculated at the last Evaluate() call. + /// The array matches the mChannels array of the aiAnimation. const std::vector &GetTransformations() const { return mTransforms; } -protected: +private: const aiAnimation *mAnim; double mLastTime; std::vector> mLastPositions;