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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 |